Skip to content
TypeScript ts advanced-types 4 min read

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.

PatternCaptures
(infer E)[]array element type
Promise<infer R>resolved value type
(...a: infer P) => anyparameter tuple
(...a: any[]) => infer Rreturn 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.

infer is only valid inside the extends clause of a conditional type. Writing it anywhere else is a syntax error.

Best Practices

  • Use infer to extract types rather than retyping structures by hand.
  • Fill non-captured pattern slots with any / any[] as permissive wildcards.
  • Add extends constraints to inferred variables to validate what you capture.
  • Combine infer with 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, and Awaited when they already fit.
Last updated June 29, 2026
Was this helpful?