Utility Types & Mapped Types: The Standard Library
The built-in utility types are not magic — they are one-liners built with mapped types. Reading their source and building your own reveals the full power of type-level transformation and eliminates the need to reach for `any` in complex object manipulation.
TypeScript Mastery
Utility Types & Mapped Types: The Standard Library
Types are a specification language — not an annotation layer. The built-in utility types are the most visible part of that language's standard library. But most developers treat them as magic incantations — Partial<T> makes everything optional, Readonly<T> makes everything readonly, and that's that. Read the source code. Partial<T> is three tokens: { [K in keyof T]?: T[K] }. Understanding the mechanism means you can compose new utilities, not just consume existing ones.
1. Mapped Types — The Mechanism
A mapped type iterates over the keys of a type and transforms each property. The syntax is deliberately similar to a for...in loop:
Kis a type variable bound to each key ofTin turnkeyof Tproduces the union of all keys ofTas literal string types- The right side can reference
T[K](the type of the current property)
1.1 Reading the Source of Partial<T>
1.2 The Full Utility Type Taxonomy
| Type | Source Implementation | What It Does |
|---|---|---|
Partial<T> |
{ [K in keyof T]?: T[K] } |
Makes all properties optional |
Required<T> |
{ [K in keyof T]-?: T[K] } |
Removes optionality from all properties |
Readonly<T> |
{ readonly [K in keyof T]: T[K] } |
Makes all properties readonly |
Pick<T, K> |
{ [P in K]: T[P] } |
Keeps only the listed keys |
Omit<T, K> |
Pick<T, Exclude<keyof T, K>> |
Removes the listed keys |
Record<K, V> |
{ [P in K]: V } |
Creates an object type with keys K and values V |
Exclude<T, U> |
T extends U ? never : T |
Removes U from union T |
Extract<T, U> |
T extends U ? T : never |
Keeps only U from union T |
NonNullable<T> |
T extends null | undefined ? never : T |
Removes null and undefined |
ReturnType<T> |
T extends (...args: any) => infer R ? R : any |
Extracts the return type of a function |
Parameters<T> |
T extends (...args: infer P) => any ? P : never |
Extracts the parameter types as a tuple |
Awaited<T> |
Recursive conditional (unwraps Promise chains) | Unwraps Promise<T> recursively |
2. Modifiers — +?, -?, +readonly, -readonly
Mapped types support modifier tokens to add or remove optionality and readonly-ness:
The -? modifier is what makes Required<T> work. Without it, a plain mapped type preserves the ? modifier from the source — { [K in keyof T]: T[K] } produces a type with the same optionality as T. The -? strips it.

3. Key Remapping with as
You can rename or filter keys in a mapped type using the as clause:

4. Homomorphic vs Non-Homomorphic Mapped Types
This distinction determines whether a mapped type preserves modifiers from the source type:
All of TypeScript's built-in utility types that iterate keyof T are homomorphic. Record<K, V> is non-homomorphic because it takes the keys as a fresh parameter, not from a source type's keyof.
5. Building Deep Utilities
Flat mapped types only transform the top level. To recurse into nested objects, combine mapped types with conditional types:
![Tree diagram showing DeepReadonly<Config> recursion. Root node: Config. Two child nodes: server and auth. Each has a dashed DeepReadonly<> call wrapping it. Under server: leaf nodes host: string and port: number, both with a readonly tag. Under auth: leaf nodes secret: string and expiresIn: number, both with a readonly tag. A decision diamond at each branch node reads: 'Is T[K] an object (non-function)? → recurse : apply readonly to leaf'. Caption: 'DeepReadonly<T> combines a mapped type with a recursive conditional — the conditional selects between recursion and the base case at each property'.](https://pub-74778554195b4df89d82f0d61988355a.r2.dev/assets/blog/frontend/typescript-utility-mapped-types/fig-03.png)
Summary
| Concept | Rule |
|---|---|
| Mapped type | { [K in keyof T]: Transform } — iterates keys and transforms each property |
+? / -? |
Add or remove optionality; -? is what makes Required<T> work |
+readonly / -readonly |
Add or remove the readonly modifier independently of optionality |
Key remapping (as) |
Rename keys with a template literal or filter them by mapping to never |
| Homomorphic | Iterates keyof T — preserves source modifiers; all built-in utilities are homomorphic |
| Non-homomorphic | Iterates a fresh key union — does not preserve source modifiers |
DeepReadonly<T> |
Requires a recursive conditional type; flat mapped types only transform one level |
What's Next
In Part 5, we cover Conditional Types &
infer— the type-level equivalent of if-else and pattern matching. They are what powerReturnType<T>,Parameters<T>,Awaited<T>, and every type-level algorithm in major TypeScript libraries.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.