Concept 4 of 12
Mapped types
A mapped type is a loop over a union of keys that builds an object. Partial, Required, Readonly and Pick are all one line of this, and once the shape is familiar you can write any of them from memory.
- Expression
Flags<"read"> - Resolves to
{ read: boolean } - Expression
Flags<"read" | "write"> - Resolves to
{ read: boolean; write: boolean }
Each member of the key union becomes one property. The value rule is applied to every key.
Type definition
type Flags<K extends string> = { [P in K]: boolean }The shape
type MyPartial<T> = { [K in keyof T]?: T[K] } // ^^^^^^^^^^^^^^ ^ ^^^^ // key set | value type // modifier
Read it as: for every K in this key set, produce a property named K whose type is that expression. The key set is any union of string, number or symbol — usually keyof T, sometimes a parameter, occasionally something computed.
Modifiers add and subtract
type Optional<T> = { [K in keyof T]+?: T[K] } // add ? type Required<T> = { [K in keyof T]-?: T[K] } // remove ? type Frozen<T> = { readonly [K in keyof T]: T[K] } type Thawed<T> = { -readonly [K in keyof T]: T[K] }
Mapping over a bare keyof T is *homomorphic*, which means existing ? and readonly modifiers are copied across automatically. That is why an identity mapping changes nothing, and why removing a modifier needs an explicit minus.
Renaming keys with as
type Getters<T> = { [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K] } // A key renamed to never disappears — this is how you filter. type OnlyStrings<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] }
The as clause rewrites each key as the mapping runs. A key rewritten to never cannot exist, so the property is dropped — which is the only way to remove a property from an object type.
The common wrong answer
// Intent: keep only the string-valued properties. type OnlyStrings<T> = { [K in keyof T]: T[K] extends string ? T[K] : never } // Every key survives; the rejected ones just hold never. // { name: string; age: never }
Filtering on the value side cannot remove a key, only empty it. Filtering happens in the key clause with as ... : never, and mixing the two up produces a type that looks filtered and enforces nothing.
Exercise
Make every property of T a string, keeping the same keys.
Try it
1type User = { id: number; admin: boolean }Stringify<User>—→ { id: string; admin: string }
Takeaway
{ [K in Keys]: Value } is the loop. Modifiers attach to the property, as rewrites the key, and never as a key is how things disappear.
Practiced in
- #212Rebuild PickPar 2
- #219Rebuild PartialPar 2
- #226Rebuild ReadonlyPar 2
- #235PickByTypePar 3
- #236GettersPar 3
- #241Rebuild RequiredPar 2
- #242MergePar 3
- #246EntriesPar 4
- #247Omit by typePar 3
- #249Optional, but only thesePar 3
- #256InvertPar 3
- #257Tuple to objectPar 3
- #258Which keys are optionalPar 4
- #263From entriesPar 3
- #270All object valuesPar 3
- #274Replace union keysPar 4
- #278Camelize the keysPar 3

