Mapped Types are types that are dynamically constructed by transforming (renaming, modifying) all properties of another type. The syntax is based on the in construct:
type Readonly<T> = { readonly [K in keyof T]: T[K]; } type User = { name: string; age: number; } const u: Readonly<User> = { name: 'Eve', age: 22 }; u.name = 'Bob'; // Error: name is read-only
Nuances:
-? or +? keywords.Example with all modifiers:
type PartialMutable<T> = { -readonly [K in keyof T]?: T[K]; };
"When applying a mapped type with the optional modifier, does it only affect the top-level properties or also nested objects?"
Answer: No, a mapped type with the optional ? only affects top-level properties. Nested objects need to be transformed separately, often using recursion or additional mapped types.
Example:
type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]; };
Story
In one project, to save time, the standard Partial<T> was used for a deep form. However, the fields at the second and third levels were not made optional, leading to unexpected runtime errors when nested keys were absent.
Story
There was an attempt to remove readonly properties only in the child objects by applying the mapped type only to the top level:
type Mutable<T> = { -readonly [K in keyof T]: T[K] }
As a result, fields of type { readonly foo: { readonly bar: number } } remained unchanged in nesting, which confused the team and complicated maintenance.
Story
In a complex data model, nested mapped types were applied to intersect several utility types (e.g., Readonly & Partial). Due to the incorrect order of their composition, unexpected type compatibility conflicts arose, and the compiler started generating confusing error messages.