RizTech Academy logo
RizTech Academy
The Type System in DepthLesson 4 of 630 min

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 (an infer on the return position).
  • A function's parameters → Parameters (an infer on the parameters).
  • A promise's resolved value → Awaited (an infer inside Promise<>).
  • An array's element type → infer inside [].
  • A tuple's parts → infer at 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

  1. Write ElementType<T> with infer and test it on string[], number[], and a non-array.
  2. Write ReturnType<T> yourself and confirm it matches the built-in on a function's typeof.
  3. Write UnwrapPromise<T> and unwrap Promise<string> and a non-promise.
  4. Write FirstParam<T> capturing a function's first parameter type.
  5. Write First<T> and Last<T> for tuples, then FirstTwo<T> capturing two elements.
  6. Read the built-in Parameters definition and identify the infer.
  7. Find a place where a type duplicates a function's return type, and replace it with ReturnType<typeof fn>.

Official documentation

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