Introduction
The infer keyword is your Swiss army knife for extracting types from complex structures. Beyond the basic ReturnType pattern, infer can extract promise results, tuple elements, function parameters, constructor types, and more. This lesson explores the most useful infer patterns you will encounter in production TypeScript.
Key Concepts
inferin function signatures: Extract return types, parameter types, orthistypes from functions.inferin promise unwrapping: Recursively unwrapPromise<Promise<T>>to get the innermost type.inferin tuple positions: Extract the first, last, or rest elements of a tuple.- Multiple
inferin one condition: You can use severalinferdeclarations in a singleextendsclause.
Real World Context
TypeScript's built-in ReturnType, Parameters, ConstructorParameters, and Awaited are all built with infer. When you work with higher-order functions, middleware chains, or promise-based APIs, knowing how to extract types with infer saves you from manual type declarations that break when the source type changes.
Deep Dive
Extracting Function Return Types
The classic infer pattern extracts the return type of a function:
typescripttype MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never; type A = MyReturnType<() => string>; // string type B = MyReturnType<(x: number) => void>; // void
The infer R in the return position tells TypeScript to capture whatever type appears there.
Extracting Function Parameters
Similarly, you can extract all parameter types as a tuple:
typescripttype MyParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never; type C = MyParameters<(a: string, b: number) => void>; // [a: string, b: number]
The result is a tuple type with labeled elements matching the parameter names.
Promise Unwrapping
A single level of promise unwrapping is straightforward:
typescripttype UnwrapPromise<T> = T extends Promise<infer U> ? U : T; type D = UnwrapPromise<Promise<string>>; // string type E = UnwrapPromise<Promise<Promise<number>>>; // Promise<number> (only one level!)
For deep unwrapping (like the built-in Awaited), you need recursion:
typescripttype DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T; type F = DeepAwaited<Promise<Promise<Promise<string>>>>; // string
The recursive call keeps unwrapping until the type is no longer a Promise.
Tuple Element Extraction
infer combined with variadic tuples lets you extract parts of a tuple:
typescript// First element type Head<T extends any[]> = T extends [infer First, ...any[]] ? First : never; // Last element type Last<T extends any[]> = T extends [...any[], infer L] ? L : never; // All elements except the first type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never; type G = Head<[string, number, boolean]>; // string type H = Last<[string, number, boolean]>; // boolean type I = Tail<[string, number, boolean]>; // [number, boolean]
These patterns are the building blocks for type-level list operations.
Multiple Infer Declarations
You can use multiple infer keywords in a single conditional:
typescripttype FirstAndLast<T extends any[]> = T extends [infer F, ...any[], infer L] ? [F, L] : never; type J = FirstAndLast<[1, 2, 3, 4]>; // [1, 4]
TypeScript infers each variable independently based on its position in the pattern.
Common Pitfalls
- Expecting deep unwrapping from a single infer —
Promise<infer U>only unwraps one layer. For nested promises, you need recursion or the built-inAwaitedtype. - Forgetting that infer only works inside extends — You cannot write
type X = infer T. Theinferkeyword is only valid in the condition part of a conditional type.
Best Practices
- Constrain the generic before inferring — Use
T extends (...args: any) => anyto ensureTis a function before trying to infer its parts. This gives better error messages when the type does not match. - Prefer built-in utility types when available — Use
ReturnType<T>,Parameters<T>, andAwaited<T>instead of reimplementing them. Write custominferpatterns only for shapes the built-ins do not cover.
Summary
infercaptures type variables from withinextendsclauses.- Use it to extract return types, parameter types, promise contents, and tuple elements.
- For deep unwrapping, combine
inferwith recursion. - Multiple
inferdeclarations in one condition extract multiple parts simultaneously.
Code Examples
// Extract the resolved type of an async function's return value
type AsyncReturnType<T extends (...args: any) => Promise<any>> =
T extends (...args: any) => Promise<infer R> ? R : never;
// Usage with real async functions
async function getUser(id: number) {
return { id, name: "Alice", role: "admin" as const };
}
async function getPosts() {
return [{ id: 1, title: "Hello" }, { id: 2, title: "World" }];
}
type User = AsyncReturnType<typeof getUser>;
// { id: number; name: string; role: "admin" }
type Posts = AsyncReturnType<typeof getPosts>;
// { id: number; title: string }[]