TypeScript

Mapped types

Partial, Readonly, and Record aren't special compiler magic — they're all built out of one general mechanism you can use yourself: looping over a type's keys to build a new type, the same way a map() loops over an array's elements.

Advanced

3 min read

The mechanism, stripped down to its simplest form

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};
 
interface User {
  id: number;
  name: string;
}
 
type PartialUser = MyPartial<User>;
// { id?: number; name?: string; } — this is a hand-written version of the real Partial<T>

[K in keyof T] is a mapped type: it iterates over every key in keyof T (the union of T's property-name literal types, from the generics-constraints lesson), and for each one, produces a corresponding property in the new type. T[K] (an indexed access type) looks up that specific property's real type on T. This is genuinely the same mechanism the built-in Partial<T> uses internally — the utility types from the previous lesson aren't compiler-privileged magic, they're mapped types someone already wrote, shipped with TypeScript so nobody has to write them again.

Modifiers: adding and removing readonly/? explicitly

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};
 
type MyRequired<T> = {
  [K in keyof T]-?: T[K]; // the -? REMOVES optionality instead of adding it
};
 
type MyMutable<T> = {
  -readonly [K in keyof T]: T[K]; // the -readonly REMOVES readonly-ness
};

readonly before the mapped key adds that modifier to every generated property, mirroring the built-in Readonly<T>. The - prefix on a modifier (-?, -readonly) does the opposite — it strips that modifier from every property, even if the source type already had it. This is how Required<T> is actually implemented internally: not by "not adding an optional marker," but by explicitly removing whatever optionality the source type already declared.

as: renaming keys while mapping — key remapping

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
 
interface Person {
  name: string;
  age: number;
}
 
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }

The as clause inside a mapped type lets you compute a different key name for each generated property, instead of reusing the original key as-is — here, combined with a template literal type (`get${...}`, which builds string literal types the same way a JavaScript template string builds runtime strings) and the built-in Capitalize<T> utility, to programmatically derive getName/getAge from name/age. This is genuinely advanced territory, but it's exactly the mechanism behind auto-generated getter/setter APIs and ORMs that derive a rich, fully-typed API surface from a single base type definition.

Where this matters: deriving one type from another instead of maintaining two by hand

interface UserForm {
  name: string;
  email: string;
  age: number;
}
 
// Without mapped types — a second, hand-maintained type that WILL drift eventually
interface UserFormErrors {
  name?: string;
  email?: string;
  age?: string;
}
 
// With a mapped type — automatically stays in sync with UserForm forever
type FormErrors<T> = {
  [K in keyof T]?: string;
};
type UserFormErrors2 = FormErrors<UserForm>;

The real, practical payoff of mapped types isn't writing your own Partial-style utility from scratch (the built-in ones already cover that) — it's this pattern: deriving a related but structurally different type (here, "the same fields, but each one optionally holds an error message string instead of its real value") directly from a single source-of-truth type, so adding a field to UserForm automatically, silently, updates UserFormErrors2 too, with zero risk of the two definitions quietly drifting apart the way two independently hand-written interfaces eventually do.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What does `[K in keyof T]` actually do inside a mapped type?

2. What does the `-` prefix do in a modifier like `[K in keyof T]-?: T[K]`?

3. What is the real, practical payoff of mapped types described in this lesson?