Q32 of 38 · TypeScript
What are advanced uses of the `infer` keyword in TypeScript conditional types?
Short answer
Short answer: `infer` captures a type at a matched position inside a `extends` clause. Advanced uses: extracting tuple element types, unwrapping nested generics, inferring callback parameter types, and building deep-unwrap utilities. TypeScript 4.7 added support for `infer` with variance annotations.
Detail
The infer keyword is used inside the extends clause of a conditional type to declare a type variable that TypeScript fills in during pattern matching.
Inferring from function types:
- Return type:
F extends (...args: any) => infer R ? R : never - First parameter:
F extends (first: infer P, ...rest: any[]) => any ? P : never - All parameters as tuple:
F extends (...args: infer A) => any ? A : never
Inferring from Promise: T extends Promise<infer U> ? U : T — basis for Awaited<T>
Inferring from arrays/tuples:
- Element type:
T extends Array<infer E> ? E : never - First element:
T extends [infer First, ...any[]] ? First : never - Last element (TypeScript 4.7):
T extends [...any[], infer Last] ? Last : never
Inferring from object types:
- Value type at key:
T extends Record<string, infer V> ? V : never
Multiple infer in one conditional: TypeScript resolves all infer variables in one pattern match, allowing complex destructuring in a single conditional.
// EXAMPLE
// Extract tuple elements
type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer T] ? T : never;
type H = Head<[string, number, boolean]>; // string
type T = Tail<[string, number, boolean]>; // [number, boolean]
// Unwrap nested Promise
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T;
type D = DeepAwaited<Promise<Promise<string>>>; // string
// Extract callback param from function
type CallbackParam<F> = F extends (cb: (arg: infer A) => any) => any ? A : never;
declare function listen(cb: (event: MouseEvent) => void): void;
type Evt = CallbackParam<typeof listen>; // MouseEvent
// Infer value type from Record
type RecordValue<T> = T extends Record<string, infer V> ? V : never;
const config = { a: 1, b: 2 } as const;
type V = RecordValue<typeof config>; // 1 | 2