Q32 of 38 · TypeScript

What are advanced uses of the `infer` keyword in TypeScript conditional types?

TypeScriptSeniortypescriptinferconditional-typesgenericsadvanced-typesutility-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

// WHAT INTERVIEWERS LOOK FOR

Comfort with multiple infer positions in one conditional. Tuple inference with rest elements. Deep-unwrap recursion. These are genuinely expert-level type gymnastics — a correct, confident answer signals deep TypeScript internals knowledge.

// COMMON PITFALL

Using `infer` outside a conditional type extends clause — it is only valid inside the extends condition. Also: recursive conditional types can cause 'Type instantiation is excessively deep' errors — TypeScript limits recursion depth.