Skip to content
advanced Phase 11 · TypeScript Advanced Patterns

Type-Level Programming

Write complex type-level logic with conditional and mapped types.

1h 15m
0 problems
Topic Progress 0%

Conditional and Mapped Types

Conditional and Mapped Types

Type-level programming treats types as a compile-time language. The two primitives are the conditional type T extends U ? X : Y and the mapped type { [K in keyof T]: ... }.

type IsString<T> = T extends string ? true : false;
type Readonly2<T> = { readonly [K in keyof T]: T[K] };

A conditional distributes over a naked type parameter: IsString<string | number> runs the branch per member and yields true | false. Wrapping the parameter in a tuple ([T] extends [string]) disables distribution when you want the union checked as one type. Mapped types iterate keyof T; modifiers like readonly and ? can be added or stripped with +/-.

Type Utilities You Actually Write

Type Utilities You Actually Write

Production code needs small helpers more than clever tricks:

type ReturnOf<F> = F extends (...a: any[]) => infer R ? R : never;
type NoNull<T> = T extends null | undefined ? never : T;
type Unwrap<T> = T extends Promise<infer U> ? Unwrap<U> : T;
type Suffix<S extends string> = `${S}Service`;

infer binds whatever the compiler matched at that position and reuses it in the true branch; never in the false branch erases non-matching union members, which is exactly how Exclude works. Template literal types turn string unions into parseable keys — route maps and event names use them to reject typos at compile time rather than at runtime.

Pitfalls and Interview Angles

Pitfalls and Interview Angles

Distributivity bites. type T<X> = X extends string ? X : never applied to string | number keeps string, because each union member is checked alone. Expecting a boolean here is the classic mistake.

never vanishes. A branch returning never deletes that member from the result union — useful for Exclude, surprising otherwise.

Depth limits. Deeply nested instantiation surfaces Type instantiation is excessively deep; tail-recursive conditional types (TS 4.5+) run up to 1000 steps before erroring.

Interview: implement DeepReadonly, explain tuple-wrapping to stop distribution, and state why keyof any is string | number | symbol.

Quiz

1. What does Exclude<string | number | boolean, string> resolve to?

Question 1 options

2. Which mapped type removes optionality from every property of T?

Question 2 options

3. What does string | number extends string ? true : false evaluate to?

Question 3 options

Flashcards

Question

Conditional type

Answer

T extends U ? X : Y — a compile-time if that selects a type branch.

Question

Mapped type

Answer

{ [K in keyof T]: ... } — rebuilds an object type key by key; supports readonly/? modifiers.

Question

Distributivity

Answer

A conditional over a naked type parameter evaluates per union member; wrap in a tuple to disable.

Question

infer

Answer

Binds a matched position inside an extends clause for reuse in the true branch.

Revision Notes

Key Takeaways

  • 1. Conditional and mapped types are the primitives; everything else is composed from them
  • 2. Union distribution is the most common source of surprising type-level results
  • 3. Recursive utilities need tail-recursion or they hit TypeScript's instantiation depth limit

Interview Tips

  • • Write the utility live: state the distribution rule first, then the tuple-wrapper fix
  • • Name a real use case (route keys, API payloads) instead of reciting syntax

Cheat Sheet

T extends U ? X : Y; [K in keyof T] with +/?/- modifiers; naked param distributes, [T] does not; infer binds positions; never erases members.

Continue Learning