⚡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>;
// numberUnwrapping 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 directlyCommon 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