Utility Types
Date: 2026-09-28
The built-in utility types aren’t magic — each is a two-line mapped or conditional type you could write yourself. Learn the two building blocks and the dozen utilities stop being a list to memorise and become a pattern you can read, adapt and extend.
Utility types are generic type aliases shipped with TypeScript’s standard library that transform one type into another — making properties optional, picking a subset, stripping null from a union. Every one is defined in lib.es5.d.ts or a sibling, in ordinary TypeScript. They’re Generics applied to types rather than functions.
The two building blocks
Mapped types loop over keys and rebuild an object type:
type Partial<T> = { [K in keyof T]?: T[K] }
// └──┬───────┘ │ └─┬┘
// for each key │ keep the value type
// └ add optionalConditional types branch on assignability, and distribute over unions — applied to each member separately, with never results vanishing:
type Exclude<T, U> = T extends U ? never : T
Exclude<'gbp' | 'eur' | 'usd', 'usd'>
= ('gbp' extends 'usd' ? never : 'gbp') → 'gbp'
| ('eur' extends 'usd' ? never : 'eur') → 'eur'
| ('usd' extends 'usd' ? never : 'usd') → never ← drops out
= 'gbp' | 'eur'infer inside a conditional captures part of a matched type — which is how ReturnType extracts a return type from a function type.
The ones worth knowing, with their definitions
UTILITY WHAT IT DOES DEFINED AS (simplified)
─────────────────── ─────────────────────────────── ──────────────────────────────────────────
Partial<T> every property optional { [K in keyof T]?: T[K] }
Required<T> every property required { [K in keyof T]-?: T[K] }
Readonly<T> every property readonly { readonly [K in keyof T]: T[K] }
Pick<T, K> keep these keys { [P in K]: T[P] }
Omit<T, K> drop these keys Pick<T, Exclude<keyof T, K>>
Record<K, V> object with keys K, values V { [P in K]: V }
Exclude<U, X> union minus members matching X U extends X ? never : U
Extract<U, X> union members matching X U extends X ? U : never
NonNullable<T> remove null and undefined T & {}
ReturnType<F> a function's return type F extends (...a: any) => infer R ? R : any
Parameters<F> a function's parameter tuple F extends (...a: infer P) => any ? P : never
Awaited<T> unwrap Promise, recursively (recursive conditional; unwraps thenables)
NoInfer<T> block inference at this site (compiler intrinsic, 5.4 — see Generics)
The -? in Required removes the optional modifier; +/- work on readonly too.
In practice
The patterns that recur in commerce code:
- Update payloads —
Partial<Pick<Customer, 'email' | 'marketingOptIn'>>: a PATCH body may send any subset of the editable fields, and only those - Lookup tables —
Record<Currency, { symbol: string; decimals: number }>fails to compile when a currency is added to the union and not to the table, which is exhaustiveness for data rather than code - Deriving from a function you don’t own —
Awaited<ReturnType<typeof fetchOrder>>gives the resolved type without redeclaring it, so it tracks the library - Stripping internals —
Omit<Order, 'internalNotes' | 'costPrice'>for what a storefront API returns
Where they mislead
Omitdoesn’t check its keys.K extends keyof any— any string is accepted, soOmit<Order, 'totl'>compiles and omits nothing. Rename a field and everyOmitof the old name silently stops working.Pickdoes check (K extends keyof T)Omiton a union flattens it.keyof (A | B)is only the keys they share, soOmit<Payment, 'id'>loses every member-specific property. A distributive version —T extends unknown ? Omit<T, K> : never— keeps the unionPartialis shallow. Nested objects stay fully required. A deep version is a recursive mapped type, and a common source of unreadable errorsReadonlyis compile-time only and shallow;Object.freezeis the runtime equivalent, also shallowRecord<string, T>claims every key exists. Readingprices['xyz']givesT, notT | undefined, unlessnoUncheckedIndexedAccessis on — Type Safety Boundaries
When a type needs three nested utilities, name the intermediate steps as their own aliases. Same rule as code: a named intermediate is documentation.