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?
2. Which mapped type removes optionality from every property of T?
3. What does string | number extends string ? true : false evaluate to?
Flashcards
Question
Conditional type
Click to reveal answer
Answer
T extends U ? X : Y — a compile-time if that selects a type branch.
Question
Mapped type
Click to reveal answer
Answer
{ [K in keyof T]: ... } — rebuilds an object type key by key; supports readonly/? modifiers.
Question
Distributivity
Click to reveal answer
Answer
A conditional over a naked type parameter evaluates per union member; wrap in a tuple to disable.
Question
infer
Click to reveal answer
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.