The infer Keyword
The infer keyword is TypeScript's way of letting you capture a type from inside
a conditional type's extends clause and refer to it in the true branch. Think of it
as a type-level variable that TypeScript fills in automatically during pattern matching.
Without infer, conditional types can only test whether something matches a shape.
With infer, they can also extract parts of that shape.
The Problem infer Solves
Suppose you want a type that extracts the return type of a function. Without infer
you cannot do this generically, because you have no way to refer to "whatever the
return type happens to be":
// Without infer — you can only check, not extract type ReturnsString<T> = T extends (...args: any[]) => string ? true : false; // There is no way to say "give me the actual return type" without infer // The following would be a type error: // type BadReturnType<T> = T extends (...args: any[]) => ??? ? ??? : never;
infer solves this by introducing a placeholder variable inside the extends clause that TypeScript infers from the matched type.infer Syntax
The syntax is:
T extends SomeType<infer U> ? U : FallbackType
When T matches SomeType<..., TypeScript binds whatever filled that slot to U.
You can then use U in the true branch.
// infer R captures the return type of any function signature
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn1 = () => string;
type Fn2 = (x: number, y: number) => boolean;
type Fn3 = (name: string) => { id: number; name: string };
type R1 = ReturnType<Fn1>; // string
type R2 = ReturnType<Fn2>; // boolean
type R3 = ReturnType<Fn3>; // { id: number; name: string }
type R4 = ReturnType<string>; // never (string is not a function)ReturnType<T> utility works. You can inspect it yourself in lib.es5.d.ts.Extracting Return Types Manually vs ReturnType
Before infer existed (TypeScript 2.8+), extracting a function's return type
required workarounds. Today the built-in ReturnType handles it — but building it
yourself is the best way to understand infer:
// Manual implementation — identical to the built-in
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
function greet(name: string): string {
return `Hello, ${name}!`;
}
function add(a: number, b: number): number {
return a + b;
}
type GreetReturn = MyReturnType<typeof greet>; // string
type AddReturn = MyReturnType<typeof add>; // number
// Built-in equivalent
type GreetReturn2 = ReturnType<typeof greet>; // stringUnwrapping Promises
infer is perfect for stripping wrappers from types. Unwrapping a Promise is a
classic use case — and TypeScript 4.5 introduced Awaited<T> as the built-in, but
writing it yourself is instructive:
// Single-level unwrap
type UnwrapPromise<T> = T extends Promise<infer V> ? V : T;
type P1 = UnwrapPromise<Promise<string>>; // string
type P2 = UnwrapPromise<Promise<number[]>>; // number[]
type P3 = UnwrapPromise<boolean>; // boolean (not a Promise, returns T)
// Recursive unwrap — handles Promise<Promise<...>>
type DeepAwaited<T> =
T extends Promise<infer V>
? DeepAwaited<V>
: T;
type Nested = DeepAwaited<Promise<Promise<Promise<string>>>>; // string
// TypeScript 4.5+ built-in (handles PromiseLike, thenables, etc.)
type A1 = Awaited<Promise<string>>; // string
type A2 = Awaited<Promise<Promise<number>>>; // numberExtracting Array Element Types
You can use infer to pull out the element type of any array — a common need when
building generic utilities that operate on array items:
type ElementType<T> = T extends (infer E)[] ? E : never; type E1 = ElementType<string[]>; // string type E2 = ElementType<number[]>; // number type E3 = ElementType<Array<boolean>>; // boolean type E4 = ElementType<string>; // never (not an array) // Also works with readonly arrays type ElementTypeR<T> = T extends readonly (infer E)[] ? E : never; type E5 = ElementTypeR<readonly string[]>; // string type E6 = ElementTypeR<readonly [1, 2, 3]>; // 1 | 2 | 3 // Built-in equivalent: ArrayElement is not built-in, but you can use: type E7 = string[][number]; // string (indexed access type — another approach)
readonly (infer E)[] as the pattern, because readonly string[] does not extend the mutable (infer E)[].Extracting Function Parameter Types
Just as you can extract return types, you can extract parameter types. TypeScript's
built-in Parameters<T> uses this exact technique:
// Extract all parameters as a tuple
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer P) => any ? P : never;
function createUser(name: string, age: number, admin: boolean): void {}
type Params = MyParameters<typeof createUser>;
// [name: string, age: number, admin: boolean]
type FirstParam = Params[0]; // string
type SecondParam = Params[1]; // number
// Extract only the first parameter
type FirstArg<T extends (...args: any[]) => any> =
T extends (first: infer F, ...rest: any[]) => any ? F : never;
type F1 = FirstArg<typeof createUser>; // string
// Extract the last parameter
type LastArg<T extends (...args: any[]) => any> =
T extends (...args: [...infer _, infer L]) => any ? L : never;
type L1 = LastArg<typeof createUser>; // booleanExtracting from Object Types
infer is not limited to functions and arrays. You can use it to extract property
types from object shapes:
// Extract the value type of a specific key
type ValueOf<T, K extends keyof T> = T extends { [P in K]: infer V } ? V : never;
interface User {
id: number;
name: string;
roles: string[];
}
type IdType = ValueOf<User, "id">; // number
type NameType = ValueOf<User, "name">; // string
type RoleType = ValueOf<User, "roles">; // string[]
// Extract constructor parameter types
type ConstructorParams<T extends new (...args: any[]) => any> =
T extends new (...args: infer P) => any ? P : never;
class Point {
constructor(public x: number, public y: number) {}
}
type PointParams = ConstructorParams<typeof Point>; // [x: number, y: number]Nested infer Patterns
Multiple infer bindings can appear in a single extends clause. This lets you
extract several parts of a type simultaneously:
// Extract both the first argument and return type of a function
type FirstArgAndReturn<T> =
T extends (first: infer A, ...rest: any[]) => infer R
? { arg: A; ret: R }
: never;
type Fn = (name: string, age: number) => boolean;
type Info = FirstArgAndReturn<Fn>;
// { arg: string; ret: boolean }
// Unwrap nested generic: Extract<Promise<Array<T>>, T>
type UnwrapPromiseArray<T> =
T extends Promise<(infer U)[]>
? U
: never;
type PA = UnwrapPromiseArray<Promise<string[]>>; // string
type PB = UnwrapPromiseArray<Promise<number[]>>; // number
type PC = UnwrapPromiseArray<string[]>; // neverinfer bindings in one pattern as you need. Each one captures a different structural position of the matched type.Real-World Use Cases
Here are practical utility types from real TypeScript codebases, all powered by
infer:
// 1. Get the resolved value type of any thenable type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T; // 2. Flatten one level of array nesting type Flatten<T> = T extends Array<infer Item> ? Item : T; type F = Flatten<string[][]>; // string[] (one level removed) // 3. Get the type a React ref holds type RefValue<T> = T extends React.RefObject<infer V> ? V : never; // 4. Extract event handler payload type EventPayload<T> = T extends (event: infer E) => void ? E : never; type Handler = (event: MouseEvent) => void; type Payload = EventPayload<Handler>; // MouseEvent // 5. Infer keys and values from a Map type MapKey<T> = T extends Map<infer K, any> ? K : never; type MapValue<T> = T extends Map<any, infer V> ? V : never; type M = Map<string, number>; type K = MapKey<M>; // string type V = MapValue<M>; // number
infer with Union Inference
When infer can match a type in multiple positions, TypeScript unions the
candidates for co-variant positions and intersects them for contra-variant
positions (like function parameters).
// Co-variant position — TypeScript unions the candidates
type ElementsOf<T> = T extends { a: infer U; b: infer U } ? U : never;
type E = ElementsOf<{ a: string; b: number }>; // string | number
// Contra-variant position — TypeScript intersects the candidates
type IntersectFnArgs<T> =
T extends { a: (x: infer U) => void; b: (x: infer U) => void }
? U
: never;
type I = IntersectFnArgs<{
a: (x: string) => void;
b: (x: number) => void;
}>; // string & number (i.e. never)never. This is correct TypeScript behaviour — functions must be callable with the same argument type, so conflicting types intersect to never.Summary — What infer Can Extract
Pattern | Extracts |
|---|---|
T extends (...args: any[]) => infer R | Return type of a function |
T extends (...args: infer P) => any | Parameter tuple of a function |
T extends Promise<infer V> | Resolved value of a Promise |
T extends (infer E)[] | Element type of an array |
T extends Map<infer K, infer V> | Key and value types of a Map |
T extends new (...args: infer P) => any | Constructor parameter tuple |
infer to extract return types, parameters, array elements, Promise values, and more. Combined with conditional types, infer is the foundation of nearly every advanced TypeScript utility type.