The infer Keyword
The infer keyword introduces a type variable inside a conditional type’s extends clause, letting the compiler capture a piece of the matched type for you to use in the true branch. It answers the question “if this type has a certain shape, what is the part I care about?” — extracting element types from arrays, return types from functions, resolved values from promises, and much more. infer only appears within the extends clause of a conditional type, never in a standalone position. This page covers the core mechanism, common extraction patterns, multiple inference sites, and inference variance.
Basic extraction
Place infer X where a type appears in a pattern, and TypeScript binds X to whatever fills that slot when the condition matches. You then reference X in the true branch.
type ElementType<T> = T extends (infer Item)[] ? Item : never;
type A = ElementType<string[]>; // string
type B = ElementType<number[]>; // number
type C = ElementType<boolean>; // never (not an array)
The pattern (infer Item)[] says “an array of some type — call that type Item.” When T is string[], Item is inferred as string. If T is not an array, the condition fails and the false branch (never) is used.
Extracting function return and parameter types
infer shines when pulling apart function types, which is exactly how the built-in ReturnType and Parameters utilities are implemented.
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type Fn = (id: number, name: string) => boolean;
type R = MyReturnType<Fn>; // boolean
type P = MyParameters<Fn>; // [id: number, name: string]
In MyReturnType, infer R captures whatever the function returns; in MyParameters, infer P captures the entire parameter list as a tuple. The any[] and any in the non-inferred slots act as wildcards that match any function.
Unwrapping promises recursively
Combine infer with recursion to handle nested wrappers, mirroring the standard Awaited type that flattens chained promises.
type Unwrap<T> = T extends Promise<infer Inner> ? Unwrap<Inner> : T;
type A = Unwrap<Promise<number>>; // number
type B = Unwrap<Promise<Promise<string>>>; // string
type C = Unwrap<boolean>; // boolean
Each time T matches Promise<infer Inner>, the type recurses on Inner, peeling off one layer of Promise. When T is no longer a promise, recursion stops and returns T as-is.
Multiple inference sites
You can use infer more than once in a single pattern. Reusing the same name in a covariant position produces a union of the candidates; in a contravariant position it produces an intersection.
type FirstAndLast<T> = T extends [infer First, ...any[], infer Last]
? [First, Last]
: never;
type Pair = FirstAndLast<[1, 2, 3, 4]>; // [1, 4]
// Same name, covariant (union) vs contravariant (intersection)
type Covariant<T> = T extends { a: infer U; b: infer U } ? U : never;
type Contra<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
type Co = Covariant<{ a: string; b: number }>; // string | number
type Cn = Contra<{ a: (x: string) => void; b: (x: number) => void }>; // string & number
Tuple patterns like [infer First, ...any[], infer Last] let you capture positions precisely. When the same infer name appears in multiple covariant slots, TypeScript unifies the candidates into a union; in contravariant (function parameter) slots it produces an intersection.
| Pattern | Captures |
|---|---|
(infer E)[] | array element type |
Promise<infer R> | resolved value type |
(...a: infer P) => any | parameter tuple |
(...a: any[]) => infer R | return type |
[infer A, ...infer Rest] | head and tail of a tuple |
Record<string, infer V> | value type of a record |
Constrained inference
Since TypeScript 4.7 you can constrain an inferred variable with extends, narrowing what it matches and letting you infer only when a sub-condition holds.
type FirstString<T> = T extends [infer S extends string, ...any[]] ? S : never;
type A = FirstString<["hello", 1, 2]>; // "hello"
type B = FirstString<[42, "x"]>; // never (first element isn't a string)
The clause infer S extends string only binds S when the candidate is assignable to string, so a leading non-string element fails the match. This avoids a separate nested conditional just to validate the captured type.
inferis only valid inside theextendsclause of a conditional type. Writing it anywhere else is a syntax error.
Best Practices
- Use
inferto extract types rather than retyping structures by hand. - Fill non-captured pattern slots with
any/any[]as permissive wildcards. - Add
extendsconstraints to inferred variables to validate what you capture. - Combine
inferwith recursion to unwrap nested generics like promises or arrays. - Remember covariant duplicate names union and contravariant ones intersect.
- Fall back to
never(or a sensible default) in the false branch for clarity. - Prefer the built-in
ReturnType,Parameters, andAwaitedwhen they already fit.