Inference & infer
Where inference gets its answers: inference sites
Section titled “Where inference gets its answers: inference sites”When you call a generic function without type arguments, TypeScript looks at every place the type parameter appears in the parameters — each is an inference site — and computes a type that fits them all.
function pair<A, B>(a: A, b: B): [A, B] { return [a, b];}
const p = pair(1, "hi");// ^? [number, string] — A from the first arg, B from the secondWhen a parameter appears more than once, TypeScript reconciles the candidates:
function both<T>(a: T, b: T): T { return a; }
const x = both(1, 2); // T = numberconst y = both(1, "two"); // T = number | string (best common type)Understanding that inference reads argument positions explains most “why did it infer that?” moments — and why annotating a return type never changes what the arguments infer.
infer: extract a type from inside another type
Section titled “infer: extract a type from inside another type”Inside a conditional type, the infer keyword introduces a new type variable that TypeScript solves for you by pattern-matching the structure. It is how you pull a piece out of a bigger type.
The canonical example — get a function’s return type:
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type A = MyReturnType<() => number>; // numbertype B = MyReturnType<(x: string) => User>; // Usertype C = MyReturnType<string>; // never (not a function)Read infer R as: “match this type against the pattern (...args) => R, and if it fits, bind R to whatever was in the return position.” This is exactly how the built-in ReturnType<T> is defined.
The pattern generalizes: pull out any part
Section titled “The pattern generalizes: pull out any part”infer can capture any position in a structure — element types, promise values, argument types:
// element type of an arraytype ElementType<T> = T extends (infer E)[] ? E : T;type E1 = ElementType<number[]>; // numbertype E2 = ElementType<string>; // string (unchanged — not an array)
// the value a Promise resolves totype Awaited1<T> = T extends Promise<infer V> ? V : T;type V1 = Awaited1<Promise<string>>; // string
// the first parameter typetype FirstArg<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;type P1 = FirstArg<(id: number) => void>; // numberEach one is the same move: describe the shape you expect, put infer X where the interesting part lives, and TypeScript fills X in when the shape matches.
A pitfall: multiple infer in the same position
Section titled “A pitfall: multiple infer in the same position”When the same infer name appears in multiple spots, TypeScript’s behavior depends on position — it infers a union in covariant positions and an intersection in contravariant (parameter) positions. You rarely need this, but it explains occasional surprises:
type Merge<T> = T extends { a: infer U; b: infer U } ? U : never;type M = Merge<{ a: string; b: number }>; // string | number (union)For most day-to-day work, one infer per pattern is all you need.