A mapped type has the form { [K in Keys]: Transform }, iterating over a set of keys (often keyof SomeType) and applying a transformation to each property. This is exactly how TypeScript's own built-in utility types — Partial<T>, Readonly<T>, Pick<T, K> — are implemented in the standard library, not special compiler magic; anyone can write an equivalent mapped type from scratch.
A basic mapped type
keyof extracts a union of a type's property names, and the mapped type iterates over that union, producing a new type with the transformation applied to every one.
interface User {
name: string;
age: number;
}
type ReadonlyUser = {
[K in keyof User]: User[K]; // same shape, but see the readonly version below
};
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]; // this is essentially how Readonly<T> is built
};
type FrozenUser = MyReadonly<User>; // { readonly name: string; readonly age: number }Modifiers: adding or removing readonly and optional
+/- prefixes on readonly or ? let a mapped type explicitly add or strip those modifiers, rather than just preserving whatever the source type already had.
type MyPartial<T> = {
[K in keyof T]?: T[K]; // essentially how Partial<T> is built
};
type MyRequired<T> = {
[K in keyof T]-?: T[K]; // the -? REMOVES optionality (essentially Required<T>)
};Key remapping with as
The as clause (introduced in TypeScript 4.1) lets a mapped type rename keys during the transformation, not just transform their values — useful for generating a companion type with prefixed or derived property names.
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }