infer Keyword Basics
Use infer inside conditional types to extract return types, promise payloads, array elements, and function parameters.
- typescript
- infer-keyword
infer appears only in conditional types. It declares a type variable to be inferred from a matched shape — “if T looks like this, bind that piece to R.”
type ReturnOf<F> = F extends (...args: never[]) => infer R ? R : never;
type R = ReturnOf<() => number>; // number
Docs: Conditional Types — infer.
Why not always use built-ins?
Prefer stdlib when it exists:
| Built-in | Role |
|---|---|
ReturnType<F> |
Function return |
Parameters<F> |
Parameter tuple |
ConstructorParameters<C> |
Constructor args |
InstanceType<C> |
Instance of constructor |
Awaited<T> |
Unwrap promises recursively |
Learn infer to build the next helper those don’t cover, and to read library types.
Extract array element
type Elem<T> = T extends readonly (infer E)[] ? E : never;
type A = Elem<string[]>; // string
type B = Elem<readonly number[]>; // number
Unwrap Promise
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type U = Unwrap<Promise<User>>; // User
Recursive (like Awaited):
type DeepAwait<T> = T extends Promise<infer U> ? DeepAwait<U> : T;
Function parameters
type Params<F> = F extends (...args: infer P) => unknown ? P : never;
type P = Params<(a: string, b: number) => void>; // [string, number]
Object property by pattern
type PropType<T, K extends PropertyKey> = T extends Record<K, infer V> ? V : never;
type N = PropType<{ age: number }, 'age'>; // number
Multiple infer positions
type MapOrigin<T> = T extends Map<infer K, infer V> ? [K, V] : never;
type Pair = MapOrigin<Map<string, User>>; // [string, User]
Distributive behavior
Bare T in T extends … infer … distributes over unions:
type UnwrapArray<T> = T extends (infer E)[] ? E : T;
type X = UnwrapArray<string[] | number[]>; // string | number
Wrap in a tuple to block distribution when needed: [T] extends [(infer E)[]] ? E : T.
Practical frontend helper
type EventProp<T> = T extends `on${string}`
? T extends keyof React.JSX.IntrinsicElements['button']
? never
: T
: never;
// More useful: extract payload from a typed action map
type ActionMap = {
login: { userId: string };
logout: undefined;
setTitle: { title: string };
};
type PayloadOf<T extends keyof ActionMap> = ActionMap[T];
// With infer from a handler type:
type HandlerPayload<H> = H extends (payload: infer P) => void ? P : never;
Reading error messages
When inference fails, the false branch (never or fallback) appears. Split aliases:
type Step = MyFn extends (...a: infer P) => infer R ? [P, R] : never;
Hover Step in the IDE instead of debugging a 40-line compound type.
Footguns
inferoutside conditionals — syntax error.- Using
any[]in patterns — can weaken inference; prefernever[]orunknown[]carefully. - Reimplementing
ReturnType— use the built-in. - Over-recursive infer — hit instantiation depth; simplify.
// Prefer
type R = ReturnType<typeof loadUser>;
// over hand-rolled infer for the same thing
Interview out-loud answer
“infer introduces a type variable inside a conditional type to capture part of a matched shape — return types, promise values, element types. Built-ins cover common cases; I write custom infer helpers for domain-specific extraction and watch distribution over unions.”
Related on this site
- Conditional types intro
- Utility types Partial Pick Omit
- Mapped types intro
- Generics basics
- Function overloads
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.