Constructs
Mapped types
A loop over a union of keys that builds an object type.
What it is
Read `{ [K in Keys]: Value }` as: for every `K` in this key set, produce a property named `K` whose type is that expression. Two independent decisions per key — what it is called, via the optional `as` clause, and what it holds. `Partial`, `Required`, `Readonly`, `Pick` and `Record` are each one line of this.
Examples
Stringify<User>
→{ id: string; name: string; }Prefixed<User>
→{ getId: number; getName: string; }The `as` clause renames the key. This is how a mapped type produces names that were not in the input.
Filtered<User>
→{ name: string; }Renaming a key to `never` removes it. That is the only way a mapped type can drop a property.
Each resolved type above was printed by TypeScript 5.9.3, not written by hand.
What it does not do
- It cannot remove a key from the value side. Producing `never` as a property *type* leaves the key present and unfillable, which is not the same as absent.
- It stops preserving modifiers the moment the key set is computed. `[K in keyof T]` is homomorphic and keeps `readonly` and `?`; add an `as` clause or union the keys and they are dropped unless you re-add them.
Takeaway
Keys are decided in the key clause and values in the value clause. Most mapped-type bugs are one of those two answers accidentally used for the other.