Master

Advanced Inference and Conditional Types

Build sophisticated type utilities using conditional types, inference, and distribution.

What you will see

[ [ 'x' ], [ 5 ] ]

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Conditional types, `infer`, and distribution all operate at compile time and leave no runtime trace. These snippets are DISPLAY-ONLY; the equivalent plain-JS behavior is shown alongside.

1

Extract and Exclude

const all = ["a", "b", "c"]; const filtered = all.filter(x => ["a", "c"].includes(x)); console.log("Extract:", filtered); const excluded = all.filter(x => !["a", "c"].includes(x)); console.log("Exclude:", excluded);

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Extract: [ 'a', 'c' ] Exclude: [ 'b' ]
2

Writing ReturnType

function id(n) { return String(n); } console.log("id is a function:", typeof id === "function");

Before you run it, say out loud what it will print. Then open the answer.

What it prints
id is a function: true
3

Distributive conditional types

const values = ["x", 5]; const arrays = values.map(v => [v]); console.log(arrays);

Before you run it, say out loud what it will print. Then open the answer.

What it prints
[ [ 'x' ], [ 5 ] ]

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A conditional type inspects a source type, infers a piece of it, and emits a derived type that flows into later usage.
1 Source type T flows in
↓
2 Conditional matches against a pattern
↓
3 infer captures the matching piece
↓
4 Derived type is emitted and reused
Why this works
The infer keyword

infer is what makes conditional types powerful, it captures an unknown type:

type ElementOf<T> = T extends (infer E)[] ? E : never;
type E = ElementOf<string[]>;  // string

infer E in T extends (infer E)[] binds E to the element type. Combined with distribution, this builds a whole ecosystem of reusable utilities.

Building a Partial utility

Everything you use is built from these primitives:

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

This is a mapped type (from the previous lesson) combined with the ? modifier. Awaited, Awaited, Readonly, Pick, Omit, and Record all follow the same pattern.

Examples

Extract and Exclude

Built-in conditional utilities

type A = Extract<string | number, string>;  // string
type B = Exclude<string | number, string>;  // number

Extract<T, U> keeps members of T assignable to U; Exclude removes them. Both distribute over unions and rely on inference internally.

Writing ReturnType

Infer a return type

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function id(n: number): string { return String(n); }
type R = ReturnType<typeof id>;  // string

infer R inside a conditional type captures the inferred return type. typeof id turns a value into its type so you can query it.

Distributive conditional types

Unions distribute

type ToArray<T> = T extends any ? T[] : never;
type X = ToArray<string | number>;  // (string | number)[] ??

When a conditional type is applied to a union via a naked type parameter, it distributes: ToArray<string | number> becomes string[] | number[]. Wrapping T in a tuple T & {} disables distribution.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Declaration Merging and Augmentation
Previous Lesson
Declaration Merging and Augmentation