⚡IntermediateAdvanced Type System

The `infer` Keyword & Type Extraction

Use `infer` to extract sub-types from complex types - function parameters, return values, Promise resolutions, and more.

Overview

`infer` is a TypeScript keyword that only works inside the `extends` clause of a conditional type. It lets you **capture a piece of the matched type** and use it in the result branch. Think of it as a pattern-match variable: `T extends Promise<infer U>` says "if T matches Promise<something>, call that something U". If the match succeeds, U is the resolved type; the true branch can reference U. This is how the built-in `ReturnType<F>`, `Parameters<F>`, `Awaited<T>`, and `InstanceType<C>` utility types are implemented. At the intermediate level you use `infer` to extract from nested generics, function signatures, tuple positions, and conditional chains - enabling powerful type-level introspection.

Key Points

  • •`infer R` inside an extends clause captures the matched type into the variable R
  • •Only valid inside conditional types: `T extends SomePattern<infer R> ? UseR : Fallback`
  • •Multiple `infer` in one conditional: `T extends (a: infer A, b: infer B) => infer R`
  • •`ReturnType<F>` = `F extends (...args: any[]) => infer R ? R : never`
  • •`Parameters<F>` = `F extends (...args: infer P) => any ? P : never` (P is a tuple)
  • •`Awaited<T>` recursively unwraps Promise chains using infer
  • •You can use infer with tuple positions: `T extends [infer Head, ...infer Tail]`

Examples

Extracting from function types

// Built-in ReturnType recreated
type MyReturnType<F extends (...args: any[]) => any> =
  F extends (...args: any[]) => infer R ? R : never;

// Built-in Parameters recreated
type MyParameters<F extends (...args: any[]) => any> =
  F extends (...args: infer P) => any ? P : never;

// Built-in FirstParameter  -  extract only the first argument
type FirstParam<F extends (...args: any[]) => any> =
  F extends (first: infer A, ...rest: any[]) => any ? A : never;

function fetchUser(id: number, token: string): Promise<{ name: string }> {
  return Promise.resolve({ name: 'Alice' });
}

type FetchReturn = MyReturnType<typeof fetchUser>;
// Promise<{ name: string }>

type FetchParams = MyParameters<typeof fetchUser>;
// [id: number, token: string]

type FetchFirstParam = FirstParam<typeof fetchUser>;
// number

Unwrapping nested generics

// Unwrap one level of Promise
type Awaited1<T> = T extends Promise<infer U> ? U : T;

type A = Awaited1<Promise<string>>;     // string
type B = Awaited1<Promise<number[]>>;   // number[]
type C = Awaited1<string>;             // string (not a Promise)

// Recursively unwrap nested Promises (like the built-in Awaited<T>)
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T;

type D = DeepAwaited<Promise<Promise<Promise<number>>>>;  // number

// Extract array element type
type ArrayElement<T> = T extends (infer E)[] ? E : T;

type E = ArrayElement<string[]>;   // string
type F = ArrayElement<number[][]>; // number[]
type G = ArrayElement<boolean>;    // boolean

// Extract the value type of a Map
type MapValue<T> = T extends Map<any, infer V> ? V : never;

type H = MapValue<Map<string, number>>; // number
type I = MapValue<Map<string, User[]>>; // User[]

Tuple head/tail and constructor extraction

// Head  -  first element of a tuple
type Head<T extends any[]> =
  T extends [infer H, ...any[]] ? H : never;

// Tail  -  everything except the first element
type Tail<T extends any[]> =
  T extends [any, ...infer Rest] ? Rest : never;

// Last  -  last element of a tuple
type Last<T extends any[]> =
  T extends [...any[], infer L] ? L : never;

type T1 = Head<[string, number, boolean]>; // string
type T2 = Tail<[string, number, boolean]>; // [number, boolean]
type T3 = Last<[string, number, boolean]>; // boolean

// Built-in InstanceType recreated
type MyInstanceType<C extends abstract new (...args: any[]) => any> =
  C extends abstract new (...args: any[]) => infer I ? I : never;

class UserService {
  constructor(private db: string) {}
  find(id: number) { return { id }; }
}

type ServiceInstance = MyInstanceType<typeof UserService>;
// UserService  -  you get the instance type without importing the class directly

Common Mistakes

  • `infer` outside a conditional type is a syntax error - it only works inside `extends` clauses: `T extends X<infer R> ? R : never`
  • When the same `infer` variable appears multiple times for contravariant positions (like function parameters), TypeScript infers an intersection rather than a union - which can surprise you
  • Forgetting the fallback (`never`) branch - always provide both branches of the conditional or TypeScript may return `undefined` when the pattern doesn't match

Note

Read `T extends Promise<infer U> ? U : T` as "if T is a Promise, pull out what it resolves to and call it U; otherwise just use T". The pattern match is structural - TypeScript checks if T's shape fits `Promise<something>` and fills in U if it does.

Exercises

Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

What does `T extends Promise<infer U> ? U : T` return when T is `Promise<string>`?