Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Mapped Types in TypeScript

Mapped Types

~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Chapter 20 showed HOW to USE Partial/Readonly – this chapter shows how they actually work INTERNALLY, and how to build YOUR OWN, project-specific utility types following the same pattern.

The basic syntax of a mapped type

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// Applied to Book, this produces EXACTLY the same as the built-in Partial<Book>:
// { isbn?: string; title?: string; author?: Author; ... }

[K in keyof T] ITERATES over EVERY property name of T (keyof T from chapter 18) – for EACH of these keys K, a new property gets created with the same name, whose type is T[K] (the ORIGINAL type of that property). The ? additionally makes it optional – EXACTLY the definition of Partial.

Adding AND removing modifiers

// ADDING readonly (like the built-in Readonly<T>):
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

// REMOVING readonly with the minus prefix:
type Writable<T> = {
  -readonly [K in keyof T]: T[K];
};

// REMOVING optional (?) - EXACTLY the built-in Required<T>:
type MyRequired<T> = {
  [K in keyof T]-?: T[K];
};

The - prefix before readonly/? is an EXPLICIT removal instruction – useful when you want to "loosen" an already readonly or optional source type again, which isn't directly possible with the simple utility types from chapter 20.

In practice: a change log for book updates

Let's build a custom mapped type that logs, for EVERY property of Book, whether it changed – a use case NO built-in utility type covers directly:

src/types/ChangeLog.ts
export type ChangeLog<T> = {
  [K in keyof T]: boolean;
};

export function createChangeLog<T extends object>(
  before: T,
  after: T,
): ChangeLog<T> {
  const log = {} as ChangeLog<T>;
  for (const key in before) {
    log[key] = before[key] !== after[key];
  }
  return log;
}
const log = createChangeLog(oldBook, newBook);
// log: { isbn: boolean; title: boolean; author: boolean; ... }

if (log.title) {
  console.log('The title has changed!');
}

ChangeLog<Book> AUTOMATICALLY has the same property names as Book, but EACH of them is a boolean instead of the ORIGINAL type – if Book later gains a property, ChangeLog<Book> adapts AUTOMATICALLY, without you having to manually keep this mapped type in sync.

Bonus: renaming keys with as

type WithGetterPrefix<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

// Applied to { title: string; pageCount: number }:
// { getTitle: () => string; getPageCount: () => number }

The as keyword INSIDE a mapped type enables "key remapping" – here combined with Capitalize (yet ANOTHER built-in utility type, for strings) and template literal types (two chapters from now), to generate a matching getter method name FROM every property name. An advanced pattern, shown here as a preview of chapter 23.

Tipp: Rule of thumb: writing mapped types YOURSELF pays off once you need a RECURRING transformation pattern across MULTIPLE types (like our ChangeLog) that no built-in utility type covers. For ONE-OFF, specific adjustments, a simple, hand-written interface is often enough.