ProgrammingSenior TypeScript Developer

What are Mapped Types in TypeScript, and how can they be used to create flexible generic types? Discuss the nuances of their usage and potential pitfalls in detail.

Pass interviews with Hintsage AI assistant

Answer.

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:

  • Modifiers (readonly, optional) can be changed, removed, or added using the -? or +? keywords.
  • They can be applied nestedly, combined with conditional types, utility types, and generics.
  • Errors are not always easily traceable during development, especially with complex nested type hierarchy modifications.

Example with all modifiers:

type PartialMutable<T> = { -readonly [K in keyof T]?: T[K]; };

Trick Question.

"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]; };

Examples of real errors due to lack of knowledge about the nuances of the topic.


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.