Mapped types
A mapped type builds a new object type by binding a key variable over a source of keys and computing a value type for each. The homomorphic form over keyof T preserves the source's readonly and optional flags, while a bare key-union form requires you to state both modifiers explicitly.
A mapped type builds a new object type by binding a key variable over a source of keys and computing a value type for each key. It is how you transform every property of a type at once instead of rewriting the shape by hand.
{ [K in keyof T]: T[K] } // one value type per key of T
{ [K in "a" | "b"]+readonly+?: number } // a and b, each readonly and optional, typed number
If you have used mapped types in TypeScript, this is the same construct: [K in S] binds K over each key in the source S, and the type after the colon is that key's value type. You can also adjust each property's modifiers as you map.
Modifiers
A mapped type can add or remove the optional and readonly flags per key, with + and -:
+?/-?add or remove the optional flag.+readonly/-readonlyadd or removereadonly.
Whether you have to write these out depends on the key source, which is the important distinction below.
Homomorphic vs constraint form
The two forms differ in where the keys come from, and that difference decides how modifiers behave.
A homomorphic mapping has a source that is exactly keyof T over a single parameter — [K in keyof T]. Because it maps over T's own keys, it preserves each property's existing modifiers from T. A property that was readonly or optional in T stays that way in the result unless you override it. You do not have to restate the modifiers, because the mapping inherits them.
{ [K in keyof T]: T[K] } // homomorphic: T's readonly/optional flags carried through
A constraint form has an explicit key union as its source — [K in "a" | "b"]. It maps over keys you wrote down, not over any existing type, so there is nothing to inherit modifiers from. Because of that, it requires both modifiers to be explicit. Leaving one implicit is a compile-time error, since there is no source property to take the missing flag from.
{ [K in "a" | "b"]+readonly+?: number } // ok: optional and readonly both stated
{ [K in "a" | "b"]+?: number } // error: only optional stated, readonly missing
{ [K in "a" | "b"]: number } // error: neither modifier stated
Explicit modifiers override inherited ones. Even in a homomorphic mapping you can write -readonly or +? to force a flag on or off, and your instruction wins over what T carried.
The keyof unknown corner
Because a homomorphic mapping keys off keyof T, it is worth knowing what happens when T is unknown: keyof unknown is never, the empty key set. A mapping over no keys produces an empty object type rather than one key for every conceivable name. This closes off a key-explosion corner that would otherwise appear when mapping over an unverified value.
Why it works this way
The two forms exist because they know different amounts about their keys. A homomorphic mapping is written against a real source type, so it can faithfully carry that source's modifiers — and defaulting to preservation is the behavior you almost always want. A constraint form invents its keys from a bare union, so there is no honest default for the modifiers; rather than guess, the checker makes you say. The keyof unknown = never rule keeps the whole construct from producing a meaningless result when the source shape was never known.
Limitations
- Key remapping with
asnow parses, but its resolution is unverified. The[K in keyof T as ...]form that renames keys as it maps is accepted by the parser today. Whether the remapped keys are fully resolved is not yet confirmed, so treat the clause as recognized syntax rather than a guaranteed transformation. as neverfiltering parses. Dropping keys by remapping them toneveris accepted syntactically now, subject to the same resolution caveat as theasclause.- Template-literal keys parse. Computing keys with a backtick template, as in `
get${K & string}, is accepted by the parser today; as with the otheras` forms, full resolution is not yet verified.
The core homomorphic and explicit-constraint forms, with their modifiers, work today.