TypeScript 5.x Mastery Guide 2026: Advanced Types, Decorators, and Best Practices
Advertisement
TypeScript 5.x in 2026: Advanced Patterns for Production
TypeScript adoption has reached ~80% of professional JavaScript projects. This guide covers the advanced patterns that separate TypeScript beginners from experts.
- Generic Constraints and Inference
- Template Literal Types
- Mapped Types
- Conditional Types
- The satisfies Operator
- Decorators (Stage 3)
- Const Type Parameters
- Utility Types Cheat Sheet
- tsconfig Best Practices for 2026
Generic Constraints and Inference
// Basic generics
function identity<T>(value: T): T {
return value
}
// Constrained generics
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: 'Sanjeev', age: 25 }
const name = getProperty(user, 'name') // string
const age = getProperty(user, 'age') // number
// const x = getProperty(user, 'xyz') // Error!
// Infer in generics
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type AsyncData<T> = T extends Promise<infer U> ? U : T
type UserData = AsyncData<Promise<{name: string}>> // {name: string}
// Recursive generics
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
Template Literal Types
type EventName = 'click' | 'change' | 'focus' | 'blur'
type HandlerName = `on${Capitalize<EventName>}`
// "onClick" | "onChange" | "onFocus" | "onBlur"
// Route building
type Route = '/users' | '/posts' | '/comments'
type ApiRoute = `/api${Route}`
// "/api/users" | "/api/posts" | "/api/comments"
// CSS utility types
type Size = 'sm' | 'md' | 'lg' | 'xl'
type Breakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl'
type ResponsiveClass<T extends string> = T | `${Breakpoint}:${T}`
// Typed event system
type EventMap = {
'user:created': { id: string; email: string }
'user:deleted': { id: string }
'post:published': { id: string; title: string }
}
class TypedEventEmitter {
emit<K extends keyof EventMap>(event: K, data: EventMap[K]): void {
// ...
}
on<K extends keyof EventMap>(event: K, handler: (data: EventMap[K]) => void): void {
// ...
}
}
const emitter = new TypedEventEmitter()
emitter.emit('user:created', { id: '1', email: 'test@test.com' }) // ✓
emitter.emit('user:created', { id: '1' }) // Error: missing 'email'
Mapped Types
// Transform all values
type Stringify<T> = { [K in keyof T]: string }
// Conditional transformation
type Nullable<T> = { [K in keyof T]: T[K] | null }
// Filter by type
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K]
}
interface User {
id: number
name: string
email: string
age: number
}
type UserStringFields = OnlyStrings<User>
// { name: string; email: string }
// Rename keys
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
type UserGetters = Getters<User>
// { getId: () => number; getName: () => string; ... }
Conditional Types
// Basic conditional
type IsString<T> = T extends string ? true : false
// Distributive (distributes over unions)
type NonNullable<T> = T extends null | undefined ? never : T
type Clean = NonNullable<string | null | undefined | number>
// string | number
// Complex conditional
type UnpackArray<T> = T extends Array<infer U> ? U : T
type A = UnpackArray<string[]> // string
type B = UnpackArray<number> // number (passthrough)
// Flatten nested arrays
type Flatten<T extends readonly unknown[]> =
T extends readonly (infer U)[]
? U extends readonly unknown[]
? Flatten<U>
: U
: never
type Flat = Flatten<[[1, 2], [3, [4, 5]]]> // 1 | 2 | 3 | 4 | 5
The satisfies Operator
// Problem: type annotation loses literal type info
const config: Record<string, string> = {
theme: 'dark',
language: 'en',
}
config.theme // type: string, not 'dark'
// Solution: satisfies preserves literal types while checking shape
const config2 = {
theme: 'dark',
language: 'en',
} satisfies Record<string, string>
config2.theme // type: 'dark' (literal preserved!)
// Real-world: typed config
type Config = {
colors: Record<string, string>
fonts: Record<string, { family: string; weight: number }>
}
const config3 = {
colors: { primary: '#3B82F6', danger: '#EF4444' },
fonts: {
heading: { family: 'Inter', weight: 700 },
},
} satisfies Config
// Full type checking + autocomplete on values!
Decorators (Stage 3)
// TypeScript 5 supports Stage 3 decorators
function log(target: any, context: ClassMethodDecoratorContext) {
return function (...args: any[]) {
console.log(`Calling ${String(context.name)} with`, args)
const result = target.apply(this, args)
console.log(`${String(context.name)} returned`, result)
return result
}
}
function memoize(target: any, context: ClassMethodDecoratorContext) {
const cache = new Map()
return function (...args: any[]) {
const key = JSON.stringify(args)
if (cache.has(key)) return cache.get(key)
const result = target.apply(this, args)
cache.set(key, result)
return result
}
}
class MathService {
@log
@memoize
fibonacci(n: number): number {
if (n <= 1) return n
return this.fibonacci(n - 1) + this.fibonacci(n - 2)
}
}
Const Type Parameters
// TypeScript 5: const type inference
function createConfig<const T extends object>(config: T): T {
return config
}
const config = createConfig({
port: 3000,
host: 'localhost',
debug: true,
})
config.port // type: 3000 (not number!)
config.host // type: 'localhost' (not string!)
// Useful for building type-safe builders
function routes<const T extends readonly string[]>(paths: T): T {
return paths
}
const appRoutes = routes(['/home', '/about', '/contact'])
// type: readonly ['/home', '/about', '/contact']
Utility Types Cheat Sheet
// Built-in utility types
Partial<T> // All properties optional
Required<T> // All properties required
Readonly<T> // All properties readonly
Pick<T, K> // Select properties
Omit<T, K> // Remove properties
Record<K, V> // Object with K keys, V values
Exclude<T, U> // Remove from union
Extract<T, U> // Keep in union
NonNullable<T> // Remove null/undefined
ReturnType<F> // Function return type
Parameters<F> // Function parameters
Awaited<T> // Unwrap Promise
InstanceType<C> // Class instance type
// Practical combinations
type CreateInput<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'>
type UpdateInput<T> = Partial<Omit<T, 'id'>>
type SafeUser = Omit<User, 'password' | 'salt'>
type ReadonlyUser = Readonly<Pick<User, 'id' | 'name' | 'email'>>
tsconfig Best Practices for 2026
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
Enable noUncheckedIndexedAccess and exactOptionalPropertyTypes — they catch real bugs that strict alone misses.
Advertisement