AniUI Academy

Mapped Types

Build a new object type by looping over an existing type's keys with in keyof, add or strip modifiers with +/-, and rename keys with key remapping.

10 min read

Partial<T>, Readonly<T>, Pick<T, Keys> — every utility type from two lessons ago that transforms an existing object type is built from the same underlying mechanism: looping over a type's keys and describing what each resulting property should look like. That mechanism is a mapped type, and this lesson is about writing your own.

keyof: getting a type's keys, as a type

Before mapping over keys, you need a way to refer to them. keyof produces a union of a type's property names, as string literal types:

interface User {
  name: string;
  age: number;
}
 
type UserKeys = keyof User; // "name" | "age"

This isn't a runtime array — you can't .map() over it or log it directly. It's a type, existing only for the compiler, in exactly the same way every other type in this track does.

The basic mapped type

{ [K in Union]: ... } loops over every member of a union type (almost always produced by keyof) and produces a property for each one:

type Identity<T> = {
  [K in keyof T]: T[K];
};
 
type SameAsUser = Identity<User>; // { name: string; age: number } — identical to User

T[K] here is an indexed access type — "the type of property K on T" — read it the same way you'd read user[key] at the value level, just one level up, operating on types instead of values.

This particular mapped type does nothing new; it's the identity operation, included specifically because every useful mapped type starts from this exact shape and adds something to it.

Adding a modifier

Wrapping the property in ? or readonly inside the loop applies that modifier to every resulting property:

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

These are, almost verbatim, the real definitions of Partial<T> and Readonly<T> from TypeScript's own standard library — nothing about them was ever a special compiler built-in.

Removing a modifier with -

Modifiers can be explicitly added (+, or nothing — the default) or removed (-):

type MyRequired<T> = {
  [K in keyof T]-?: T[K]; // strip optionality, making every property required
};
 
interface Settings {
  theme?: string;
  fontSize?: number;
}
 
type FullSettings = MyRequired<Settings>; // { theme: string; fontSize: number }

This is the actual definition of Required<T> — a mapped type using -? to remove optionality from every property, the mirror image of Partial<T> adding it.

Filtering keys with a conditional inside the loop

Combining a mapped type with a conditional type (previous lesson) lets you select only some keys, rather than mapping over all of them:

type OnlyStrings<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};
 
interface Mixed {
  name: string;
  age: number;
  email: string;
}
 
type StringFields = OnlyStrings<Mixed>; // { name: string; email: string }

Remapping a key to never — as the age property does here, since it fails the T[K] extends string check — removes that property from the result entirely. This is the same as key remapping used above to also rename keys, just used here to conditionally drop some of them instead.

Key remapping with as, briefly previewed

Beyond filtering, as inside a mapped type can transform the key itself into a genuinely different name:

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

This uses a template literal type to build a new key name out of an existing one ("name" becomes "getName") — powerful enough, and distinct enough as its own topic, that it gets the entire next lesson.

Try it yourself

Try it yourself
Loading playground...

What to remember

  • keyof T produces a union of T's property names as string literal types — a type, not a runtime value.
  • { [K in keyof T]: T[K] } is the identity mapped type; every built-in object utility type (Partial, Required, Readonly, Pick) is a small variation on this shape.
  • +/- before ? or readonly inside a mapped type explicitly adds or removes that modifier from every resulting property.
  • Combining as with a conditional inside the loop can filter out keys entirely (mapping to never) or rename them — the subject of the next lesson.

Check yourself

4 questions · pass 3/4 to unlock Template Literal Types

up to 50
  1. 1.What does keyof T produce for interface User { name: string; age: number }?

  2. 2.What does { [K in keyof T]: T[K] } produce, for any object type T?

  3. 3.What does the -? modifier do in { [K in keyof T]-?: T[K] }?

  4. 4.What does as do in a mapped type, e.g. { [K in keyof T as get${string & K}]: T[K] }?

4 left to answer