Extracting types with infer
The infer keyword is the piece that makes conditional types genuinely powerful: it lets you capture
a type from inside another type and use it. "Give me the return type of this function." "Give me the
element type of this array." "Give me what this Promise resolves to." Each is infer at work, and each
is a built-in utility type you have used. This short, dense lesson is infer — the type-level
equivalent of destructuring.
The idea: name a piece of a type
infer R appears inside the extends clause of a conditional type, and it means "match this position,
and capture whatever type is here into a new type variable R, which I can use in the true branch":
type ElementType<T> = T extends (infer U)[] ? U : never;
type A = ElementType<string[]>; // string — U was captured as string
type B = ElementType<number[]>; // number
type C = ElementType<boolean>; // never — boolean is not an array, so no match
Read T extends (infer U)[] as: "if T matches the pattern array-of-something, call that something
U." When you pass string[], TypeScript matches it against (infer U)[], works out that U must be
string, and makes it available in the ? U branch. It is pattern-matching on types: you describe a
shape with a hole, and infer fills the hole with whatever was there.
Extracting a function's return type
The canonical use — and the definition of the built-in ReturnType:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function createUser(name: string) {
return { id: "1", name, createdAt: new Date() };
}
type NewUser = ReturnType<typeof createUser>;
// { id: string; name: string; createdAt: Date } — R captured the return type
T extends (...args: any[]) => infer R says "if T is a function returning something, capture that
something as R." So ReturnType<typeof createUser> extracts exactly what createUser returns —
without you re-typing it. This is how you keep a type in sync with a function: derive it with infer
rather than duplicate it. You can capture the parameters the same way:
type FirstParam<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;
type A = FirstParam<(name: string, age: number) => void>; // string
Unwrapping a Promise
Another everyday one — the heart of the built-in Awaited, essential for async code:
type UnwrapPromise<T> = T extends Promise<infer V> ? V : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<Promise<User>>; // User
type C = UnwrapPromise<number>; // number — not a promise, returned unchanged
T extends Promise<infer V> captures what a promise resolves to. So an async function's actual
result type — which is Promise<something> — can be unwrapped to the something. The real Awaited<T>
is more sophisticated (it handles nested promises recursively), but the core is this one infer. You
will lean on Awaited in the async module to type "what this async operation eventually gives me".
Capturing multiple pieces, and tuples
infer can appear several times, capturing multiple types, and it works beautifully on tuples:
type First<T> = T extends [infer F, ...any[]] ? F : never;
type Last<T> = T extends [...any[], infer L] ? L : never;
type A = First<[string, number, boolean]>; // string
type B = Last<[string, number, boolean]>; // boolean
// capture two at once:
type FirstTwo<T> = T extends [infer A, infer B, ...any[]] ? [A, B] : never;
type C = FirstTwo<[string, number, boolean]>; // [string, number]
[infer F, ...any[]] matches "a tuple whose first element is something (captured as F), followed by
anything". This is genuine pattern-matching on tuple structure — first element, last element, first two
— each a small infer pattern. It is how libraries extract argument types, split tuples, and build
type-safe variadic functions.
Why infer is the payoff
infer is what turns conditional types from "choose type X or Y" into "take this type apart and use
its pieces". Almost every "extract a type from a structure" you will ever need is infer:
- A function's return type →
ReturnType(aninferon the return position). - A function's parameters →
Parameters(aninferon the parameters). - A promise's resolved value →
Awaited(aninferinsidePromise<>). - An array's element type →
inferinside[]. - A tuple's parts →
inferat specific positions.
You will use the built-in versions of these constantly (ReturnType, Parameters, Awaited) and
rarely need to write your own infer — but when you do (typing a library's return, unwrapping a custom
wrapper), it is the only tool that works. And reading it fluently is essential, because library type
definitions are full of it. When you can look at T extends Promise<infer V> ? V : T and immediately
read "unwrap the promise", you can read any TypeScript type.
Check your work
What infer does. Captures a type from a position inside a conditional type's extends pattern,
into a new type variable usable in the true branch — pattern-matching on types.
How to read T extends (infer U)[]. "If T is an array of something, call that something U."
How ReturnType is built. T extends (...args: any[]) => infer R ? R : never — captures the return
type.
How to unwrap a Promise. T extends Promise<infer V> ? V : T — the core of Awaited.
Capturing multiple pieces. infer can appear several times; on tuples it matches positions —
[infer F, ...any[]] captures the first element.
The everyday built-ins infer powers. ReturnType, Parameters, Awaited, and array/tuple
element extraction.
The honest guidance. Use the built-in extractors constantly; write your own infer rarely (custom
wrappers, library returns); read it fluently always.
The one-line summary. infer takes a type apart and hands you the pieces.
Practice
- Write
ElementType<T>withinferand test it onstring[],number[], and a non-array. - Write
ReturnType<T>yourself and confirm it matches the built-in on a function'stypeof. - Write
UnwrapPromise<T>and unwrapPromise<string>and a non-promise. - Write
FirstParam<T>capturing a function's first parameter type. - Write
First<T>andLast<T>for tuples, thenFirstTwo<T>capturing two elements. - Read the built-in
Parametersdefinition and identify theinfer. - Find a place where a type duplicates a function's return type, and replace it with
ReturnType<typeof fn>.
Official documentation
- TypeScript — Inferring Within Conditional Types —
infer, from the source. - TypeScript — ReturnType and Parameters — Built on
infer. - TypeScript — Awaited — Recursive promise unwrapping.
Next: template literal types.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship