Want a faster signal before interviews? Take the Interview Ready Check →

DAY 28 OF 30

Mapped types

Yesterday, in one sentence

What is function overloading in TypeScript?

Interview question

What is the difference between Pick<T, K> and Omit<T, K>?

Today's syntax is three things you already know, stacked. Worth a quick look at each before they combine.

keyofkeyof gives a union of a type's property names.

type Hero = {
  name: string;
  level: number;
};

type HeroKey = keyof Hero;
// "name" | "level"

An indexed access typeIndexed Access Type looks up the type of one property by name.

type Level = Hero["level"];
// number

And in, inside the square brackets of the syntax below, loops over a union. This is not the JavaScript in operator used for narrowingNarrowing. It only appears here, and it maps over every member of the union, creating a property for each one, to produce a new object type.

A mapped type puts all three together.

// type TypeName = { [Key in Union]: ValueType }
type Hero = {
  name: string;
  level: number;
};

type ReadonlyHero = {
  readonly [K in keyof Hero]: Hero[K];
};
// { readonly name: string; readonly level: number }

keyof Hero is the union, so the block runs once with K as "name" and once with K as "level". Each pass creates a property with that name, and Hero[K] reads that property's original type.

Add ? or readonly in front of the key and every generated property gets that modifier. This is how Partial is written.

// map over each property of T, keep its type, make it optional
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

type PartialHero = MyPartial<Hero>;
// { name?: string; level?: number }

A minus sign in front takes the modifier away instead, so -? makes optional properties required. This is how Required is written.

// map over each property of T, keep its type, remove the optional marker
type MyRequired<T> = {
  [K in keyof T]-?: T[K];
};

type Draft = {
  name?: string;
  level?: number;
};

type FullHero = MyRequired<Draft>;
// { name: string; level: number }

A mapped type can only go in a type aliasType Alias. There is no way to write one on an interfaceInterface.

Check yourself

What is Powers<Hero> here?
type Hero = {
  flight: string
  healing: string
}

type Powers<T> = {
  [K in keyof T]: boolean
}
What is a mapped type in TypeScript?