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.
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.
1Source type T flows in
↓
2Conditional matches against a pattern
↓
3infer captures the matching piece
↓
4Derived type is emitted and reused
Why this worksThe 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>; // stringtype 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;
functionid(n: number): string { returnString(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.