The Utility Types
You already know how these are built
Section titled “You already know how these are built”TypeScript ships a standard library of utility types. By now you have seen every technique they use — so instead of memorizing them, you can read them. That is the goal of this lesson: recognize each utility and know the primitive behind it.
| Utility | What it does | How it is built |
|---|---|---|
Partial<T> | All properties optional | Mapped type with ? |
Required<T> | All properties required | Mapped type with -? |
Readonly<T> | All properties readonly | Mapped type with readonly |
Pick<T, K> | Keep only keys K | Mapped type over K + T[K] |
Omit<T, K> | Drop keys K | Pick of Exclude<keyof T, K> |
Record<K, V> | Object with keys K, values V | Mapped type over K |
Exclude<T, U> | Remove U from union T | Distributive conditional |
Extract<T, U> | Keep only U from union T | Distributive conditional |
NonNullable<T> | Remove null/undefined | Conditional type |
ReturnType<F> | The return type of F | Conditional with infer |
Parameters<F> | The parameter tuple of F | Conditional with infer |
Awaited<T> | Unwrap a Promise | Recursive conditional with infer |
The mapped-type family
Section titled “The mapped-type family”Partial, Required, Readonly, Pick, and Record are all one-line mapped types:
type Partial<T> = { [K in keyof T]?: T[K] };type Required<T> = { [K in keyof T]-?: T[K] };type Readonly<T> = { readonly [K in keyof T]: T[K] };type Pick<T, K extends keyof T> = { [P in K]: T[P] };type Record<K extends keyof any, V> = { [P in K]: V };Nothing exotic — a loop over keys, sometimes changing a modifier, sometimes looking up T[K]. Once you see this, Pick<User, "id" | "name"> is obviously “iterate those two keys, copy their value types.”
The distributive-conditional family
Section titled “The distributive-conditional family”Exclude, Extract, and NonNullable operate on unions using a conditional type that distributes over each member:
type Exclude<T, U> = T extends U ? never : T;type Extract<T, U> = T extends U ? T : never;type NonNullable<T> = T extends null | undefined ? never : T;
type A = Exclude<"a" | "b" | "c", "b">; // "a" | "c"When the checked type is a naked type parameter and you pass a union, the conditional runs once per member and unions the results. So Exclude maps each member to either never (drop it) or itself (keep it) — and never disappears from a union. Omit then falls out as Pick<T, Exclude<keyof T, K>>.
The infer family
Section titled “The infer family”ReturnType, Parameters, and Awaited capture a type out of a structure with infer:
type ReturnType<F> = F extends (...args: any[]) => infer R ? R : never;type Parameters<F> = F extends (...args: infer P) => any ? P : never;
type R = ReturnType<() => number>; // numbertype P = Parameters<(a: string, b: number) => void>; // [string, number]infer R matches the function return position and binds it to R; infer P matches the whole parameter list as a tuple. Awaited adds recursion to peel nested promises.
flowchart TB mapped["Mapped types"] --> f1["Partial, Required, Readonly, Pick, Record"] cond["Distributive conditionals"] --> f2["Exclude, Extract, NonNullable, Omit"] infer["Conditionals with infer"] --> f3["ReturnType, Parameters, Awaited"]