Capture Types with infer
Capture Types with infer
infer may only appear inside an extends clause, and it binds whatever the compiler matched in that slot for reuse in the true branch. The standard moves:
type El<T> = T extends readonly (infer E)[] ? E : never;
type First<F> = F extends (a: infer A, ...r: any[]) => any ? A : never;
type Awaited2<T> = T extends Promise<infer U> ? Awaited2<U> : T;
Inference also runs at call sites: passing [1, 2] to function f<T>(x: T) infers number[], while an as const assertion (or a const type parameter in TS 5) shifts the result to the readonly tuple readonly [1, 2].
Inference Across Unions
Inference Across Unions
Conditional inference distributes like any other conditional: T extends Promise<infer U> over Promise<A> | Promise<B> yields A | B. When the shapes differ — Promise<X> | X — you get the union of every branch match, so wrap to check the whole thing:
type Unpromised<T> = [T] extends [Promise<infer U>] ? Unpromised<U> : T;
type KV<T> = T extends Map<infer K, infer V> ? [K, V] : never;
Two independent infer slots capture two positions at once (Map<K, V> above). Inference is directional: return types flow from the implementation, while parameters flow from the arguments you pass.
Recursive Types and Limits
Recursive Types and Limits
Recursive types model trees and self-similar data:
type Json =
| string | number | boolean | null
| Json[]
| { [k: string]: Json };
type Flatten<T> = T extends Array<infer U> ? Flatten<U> | U : T;
Since TS 4.5, tail-recursive conditional types are optimized up to 1000 instantiations, so Flatten handles deep arrays; other recursion hits Type instantiation is excessively deep. Interview trap: Awaited unwraps nested promises — explain why the naive T extends Promise<infer U> ? U : T stops one level early on Promise<Promise<number>>.
Quiz
1. What does T extends Promise<infer U> ? U : T produce for Promise<Promise<number>>?
2. Where is the infer keyword allowed?
3. Which assertion infers a readonly tuple of literals instead of number[]?
Flashcards
Question
infer keyword
Click to reveal answer
Answer
Placeholder inside an extends clause that captures the matched type for the true branch.
Question
Distributed inference
Click to reveal answer
Answer
Union input + naked parameter = infer runs per member and unions the results.
Question
Tuple wrapper trick
Click to reveal answer
Answer
[T] extends [X] checks the union as one type, stopping per-member distribution.
Question
Recursive conditional types
Click to reveal answer
Answer
Tail-recursive forms are optimized up to 1000 steps (TS 4.5+); deeper hits an instantiation error.
Question
as const vs const type param
Click to reveal answer
Answer
Both infer literal/readonly tuples; the const modifier (TS 5) does it on the parameter instead of the argument.
Revision Notes
Key Takeaways
- 1. infer captures positions, not names — it reuses whatever type landed there
- 2. Distribution applies to inference too; wrap the checked type in a tuple to opt out
- 3. Recursion is supported but bounded; prefer tail-recursive forms
Interview Tips
- • Derive Awaited or ElementOf step by step and show the distributed result for a union input
- • Contrast inference at a call site (runtime arguments) with inference in a conditional (type space)
Cheat Sheet
infer lives only in extends clauses; distributes over naked params; [T] disables it; Promise