100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

Mapped Types in TypeScript

Transform every property of an existing type into a new type using `{ [K in keyof T]: ... }`, with modifiers and key remapping.

Advanced TypesAdvanced14 min readJul 8, 2026
Analogies

1. Introduction

A mapped type builds a new object type by iterating over the properties of an existing type and applying a transformation to each one. Instead of writing type ReadonlyTask = { readonly title: string; readonly done: boolean } by hand, you write a single generic rule once and TypeScript applies it to every key. Mapped types are the mechanism behind built-in utility types such as Partial<T>, Readonly<T>, Pick<T, K>, and Record<K, T>, and they are essential for writing generic, DRY type-level transformations in advanced TypeScript code.

🏏

Cricket analogy: A mapped type is like a rule that automatically marks every column of a scorecard (runs, balls, fours) as locked-in once the innings ends, instead of manually retyping a ReadonlyScorecard type for each stat.

2. Syntax

typescript
type MappedType<T> = {
  [K in keyof T]: T[K];          // identity mapping
};

type ReadonlyMapped<T> = {
  readonly [K in keyof T]: T[K];  // add a modifier
};

type OptionalMapped<T> = {
  [K in keyof T]?: T[K];          // add optionality
};

type RequiredMapped<T> = {
  [K in keyof T]-?: T[K];         // remove optionality (- modifier)
};

// key remapping with `as` (TS 4.1+)
type RenamedMapped<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

3. Explanation

The clause [K in keyof T] iterates K over the union of property names produced by keyof T, and for each K the right-hand side describes the type of the resulting property, typically T[K] (an indexed access type) to preserve the original property's type. Modifiers readonly and ? can be added or, using a - prefix (-readonly, -?), explicitly stripped from the source type — this is how Required<T> is implemented ({ [K in keyof T]-?: T[K] }) as the inverse of Partial<T>.

🏏

Cricket analogy: [K in keyof Scorecard] iterating over "runs" | "wickets" is like a scoreboard operator going column by column, keeping each field's original type with Scorecard[K]; stripping the ? with -? is like insisting every optional stat (like "notOutBonus") must now be filled in, mirroring how RequiredScorecard forces completeness.

Since TypeScript 4.1, the as clause inside a mapped type lets you remap keys entirely, producing a different property name than the source, which is how Getters<T> style APIs and Record-based lookup transformations are built. The remapped key expression is itself a template literal type, so it composes with keyof, Capitalize, Uppercase, and conditional types.

🏏

Cricket analogy: The as clause remapping "runs" to "getRuns" is like a broadcaster renaming a raw stat column into a commentary label, composing Capitalize<K> with the property name so every Getters<Scorecard> method reads "getRuns" instead of the source field.

Mapped types only iterate over keyof T. If T has an index signature (e.g. { [key: string]: number }), keyof T resolves to string | number, and the mapped result inherits an index signature rather than named properties — this differs from mapping over an object literal type with named keys.

Gotcha: applying as with a key expression that evaluates to never for some K silently drops that property from the mapped type — this is exactly how Omit<T, K> and conditional key filtering work, but it can be an easy source of confusion when a property mysteriously disappears from a derived mapped type. Always check whether your remapping expression can collapse to never.

4. Example

typescript
type Task = {
  title: string;
  done: boolean;
  priority: number;
};

type ReadonlyTask = {
  readonly [K in keyof Task]: Task[K];
};

type StringifiedTask = {
  [K in keyof Task]: string;
};

const t: ReadonlyTask = { title: "Ship v1", done: false, priority: 1 };
console.log(Object.keys(t).join(", "));

const s: StringifiedTask = { title: "Ship v1", done: "false", priority: "1" };
console.log(Object.values(s).join(" | "));

// key remapping with `as`
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type TaskGetters = Getters<Task>;
const getters: TaskGetters = {
  getTitle: () => t.title,
  getDone: () => t.done,
  getPriority: () => t.priority,
};
console.log(getters.getTitle(), getters.getDone(), getters.getPriority());

5. Output

text
title, done, priority
Ship v1 | false | 1
Ship v1 false 1

6. Key Takeaways

  • A mapped type has the form { [K in keyof T]: ... }, iterating over T's property names.
  • The readonly and ? modifiers can be added or removed (with a - prefix) inside the mapping.
  • The as clause (TS 4.1+) lets you remap or filter keys, including dropping keys by mapping them to never.
  • Mapped types are the mechanism behind Partial, Required, Readonly, Pick, and Record.
  • keyof T combined with an index signature yields string | number rather than named literal keys.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#TypeScriptProgrammingStudyNotes#Programming#MappedTypesInTypeScript#Mapped#Types#Syntax#Explanation#StudyNotes#SkillVeris