Deep Dive11 min read · Updated 2025-06-01

Advanced TypeScript Patterns Every Senior Dev Should Know

Deep dive into advanced TypeScript: branded types, template literal types, recursive types, builder pattern, variance, and type-level programming techniques used in real libraries.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

Advanced TypeScript Patterns Every Senior Dev Should Know

1. Branded Types (Nominal Typing)

TypeScript uses structural typing — two types with the same shape are interchangeable. Branded types add nominal safety:

type UserId   = string & { readonly __brand: 'UserId' }
type ProductId = string & { readonly __brand: 'ProductId' }

function createUserId(id: string): UserId { return id as UserId } function createProductId(id: string): ProductId { return id as ProductId }

function getUser(id: UserId): User { / ... / }

const userId = createUserId('user-123') const productId = createProductId('prod-456')

getUser(userId) // ✓ getUser(productId) // ❌ Error: ProductId is not assignable to UserId getUser('user-123') // ❌ Error: string is not branded UserId

Useful for IDs, validated strings, monetary values, and CSS pixel values.

2. Template Literal Types

type Alignment = 'left' | 'center' | 'right'
type Direction = 'horizontal' | 'vertical'

type ClassName = align-${Alignment} // 'align-left' | 'align-center' | 'align-right'

// Build event handler types type EventName<T extends string> = on${Capitalize<T>} type ClickHandler = EventName<'click'> // 'onClick'

// CSS property builder type CSSProperty = 'margin' | 'padding' type CSSEdge = 'Top' | 'Bottom' | 'Left' | 'Right' type CSSEdgeProperty = ${CSSProperty}${CSSEdge} // 'marginTop' | 'marginBottom' | ... | 'paddingRight' (8 combinations)

3. Recursive Types

// JSON value — any valid JSON
type JSONValue =
  | string | number | boolean | null
  | JSONValue[]
  | { [key: string]: JSONValue }

// Deep partial — every nested object's properties are optional type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }

// Deep readonly type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K] }

// File system tree type FileTree = { name: string children?: FileTree[] // recursive reference }

4. Distributive Conditional Types

// When T is a union, conditional types are distributed over each member
type IsString<T> = T extends string ? true : false
type A = IsString<string | number>  // boolean (true | false) — distributed!

// Prevent distribution with brackets type IsStringExact<T> = [T] extends [string] ? true : false type B = IsStringExact<string | number> // false — not distributed

// Use distribution to filter unions: type FilterStrings<T> = T extends string ? T : never type OnlyStrings = FilterStrings<string | number | boolean> // string

5. Variadic Tuple Types

// Spread in tuple positions
type Concat<A extends unknown[], B extends unknown[]> = [...A, ...B]
type C = Concat<[1,2], [3,4]>  // [1, 2, 3, 4]

// Type-safe curry type Curry<F extends (...args: any) => any> = F extends (first: infer A, ...rest: infer R) => infer T ? (first: A) => R extends [] ? T : Curry<(...args: R) => T> : never

6. Assertion Functions

// Narrows type for rest of scope when the function doesn't throw
function assert(condition: unknown, message: string): asserts condition {
  if (!condition) throw new Error(message)
}

function assertIsUser(val: unknown): asserts val is User { if (!val || typeof val !== 'object' || !('id' in val)) { throw new Error('Not a user') } }

const data: unknown = parseJSON(response) assertIsUser(data) console.log(data.id) // data: User ✓ — TypeScript narrows after the assertion

7. Const Type Parameters (TypeScript 5.0+)

// Without const — TypeScript widens the type
function makeArray<T>(items: T[]) { return items }
const arr = makeArray(['a', 'b', 'c'])  // string[]

// With const — infers literal types function makeArray<const T>(items: T[]) { return items } const arr2 = makeArray(['a', 'b', 'c']) // readonly ['a', 'b', 'c']

8. Satisfies Operator (TypeScript 4.9+)

const palette = {
  red:  [255, 0, 0],
  green: '#00ff00',
  blue: [0, 0, 255],
} satisfies Record<string, string | number[]>

// Without satisfies — TypeScript widens to Record<string, string | number[]> // palette.red would be string | number[], losing the specific type

// With satisfies — validates shape AND preserves narrower inferred types palette.red.map(c => c * 2) // number[] ✓ — preserved as number[] palette.green.toUpperCase() // string ✓ — preserved as string

Practice advanced TypeScript at [JSPrep Pro](/auth).

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Deep Dive
We Built a RAG-Powered AI Question Engine Into a JavaScript Interview Platform — Here's Exactly How It Works
12 min read
Build Systems
Monorepo with Turborepo vs Nx: The Complete Comparison (2025)
9 min read
Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read