ESC

Type to search the knowledge base.

Mapped Types Intro

Transform every property of a type with {[K in keyof T]: …} — the engine behind Partial, Readonly, Pick patterns, and typed form bags.

advanced3 min read
  • typescript
  • mapped-types

A mapped type builds a new object type by iterating keys:

type OptionsFlags<T> = {
  [K in keyof T]: boolean;
};

type FeatureFlags = OptionsFlags<{ darkMode: () => void; newUser: () => void }>;
// { darkMode: boolean; newUser: boolean }

If you’ve used Partial, Required, Readonly, or Record, you’ve used mapped types.

Docs: Mapped Types, keyof.

Core syntax

type Mapper<T> = {
  [K in keyof T]: T[K]; // identity map
};
  • K in … — key iteration
  • T[K] — original value type via indexed access
  • Modifiers: readonly, ?, and prefixes -readonly, -?

Reimplementing Partial and Readonly

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

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

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

-? removes optionality. -readonly removes readonly.

Key remapping (as)

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

type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }

Filter keys by remapping to never:

type OnlyStrings<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

Record

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

type RolePermissions = Record<'admin' | 'user' | 'guest', string[]>;

Prefer Record<Union, V> over index signatures when the key set is closed.

Forms and dirty fields

type FormValues = {
  email: string;
  password: string;
  remember: boolean;
};

type DirtyFlags = { [K in keyof FormValues]: boolean };
type FieldErrors = { [K in keyof FormValues]?: string };

Or partial records: Record and Partial patterns for forms.

Homomorphic mapped types

When you write { [K in keyof T]: … }, TypeScript preserves optional/readonly modifiers from T in many cases (homomorphic mapping). That keeps Partial-like behavior well-behaved on optional fields.

Combining with conditionals

type NonFunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends (...args: never[]) => unknown ? never : K;
}[keyof T];

type NonFunctionProps<T> = Pick<T, NonFunctionPropertyNames<T>>;

This pattern powers “props without methods” helpers.

Template + map for event maps

type Events = {
  click: MouseEvent;
  keydown: KeyboardEvent;
};

type Handlers = {
  [K in keyof Events as `on${Capitalize<string & K>}`]?: (e: Events[K]) => void;
};
// { onClick?: (e: MouseEvent) => void; onKeydown?: … }

Footguns

  1. Mapping string keys — produces huge/weak types; prefer key unions.
  2. Forgetting string & K — needed when K is string | number | symbol in template remaps.
  3. Excessive nesting — intermediate aliases for readability.
  4. Runtime — mapped types erase; build runtime keys with Object.keys + validation separately.

Interview out-loud answer

“Mapped types iterate keys with in keyof to transform property types — that’s how Partial and Readonly work. I use them for form error maps, flag maps, and key remapping with as. They stay in the type system only; runtime still needs real objects.”

Further reading

Related guides