TypeScript 5.x Mastery Guide 2026: Advanced Types, Decorators, and Best Practices

Sanjeev SharmaSanjeev Sharma
6 min read

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

// 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

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro