Concept 5 of 12
Modifiers
Modifiers are the small print of mapped types. They are easy to write and easy to get subtly wrong, because the default behaviour — silently copying whatever was already there — looks identical to doing nothing.
Homomorphic mappings inherit modifiers
interface Draft { title?: string; readonly id: number } // Mapping over bare `keyof T` preserves ? and readonly: type Copy<T> = { [K in keyof T]: T[K] } type Same = Copy<Draft> // { title?: string; readonly id: number } // Mapping over a computed key set does NOT: type Flat<T> = { [K in keyof T | never]: T[K] } type Bare = Flat<Draft> // { title: string; id: number }
A mapping is homomorphic only when the key set is exactly `keyof T` for a type parameter `T`. Compute the key set any other way — union it, filter it, rename with `as` — and every modifier is dropped by default.
This catches people out when writing a `Merge` type: the result loses optionality from both sources, and nothing warns you.
`-?` removes undefined too
type T = { a?: string } // a?: string | undefined type R = { [K in keyof T]-?: T[K] } // { a: string } — not { a: string | undefined }
Subtracting the optional modifier does two things at once: the key becomes required, and `undefined` is stripped from the value type. That second part is easy to forget and is usually what you wanted anyway.
The common wrong answer
// Intent: make everything optional. type Loose<T> = { [K in keyof T]: T[K] | undefined } // The key is still required — you must write { a: undefined } // rather than omitting it. Optional and "may be undefined" // are genuinely different properties.
Widening the value is not the same as making the key optional, and exact-equality checks can tell them apart. If you meant optional, the `?` belongs after the key clause.
Takeaway
Modifiers act on the property, never the value. And a mapping that touches the key set at all forgets every modifier it was not told to keep.