Mapped Types in TypeScript: Transform Every Key

Learn how mapped types in TypeScript build a new type by transforming each property of an existing one, and how Partial and Readonly are made this way.

Published September 20, 20269 min readBy ACY Partner Indonesia
Mapped Types cover with the syntax [K in keyof T]
300 × 250Ad Space AvailablePlace your ad here

Imagine you have a type that describes a user, and now you need a second version of that type where every field is optional. Then a third version where every field is read-only. You could copy the type and edit it by hand, but that gets tedious fast, and the two copies drift apart the moment someone changes one and forgets the other.

TypeScript has a cleaner answer: mapped types. A mapped type is a recipe that takes an existing type and builds a brand new one by walking over each property and transforming it. Once you understand the pattern, a lot of TypeScript’s built-in magic stops feeling like magic. In this article we’ll go slow, build the idea from its smaller pieces, and end with a worked example you can reason about line by line.

The two pieces you need first

Before mapped types make sense, two smaller tools have to click. They’re both small, so let’s get them out of the way.

keyof gives you the keys as a type

When you write keyof SomeType, TypeScript hands you back a type made of all the property names (the keys) of that type, joined together with | (which reads as “or”).

type User = {
  name: string;
  email: string;
  age: number;
};

type UserKeys = keyof User;
// UserKeys is: "name" | "email" | "age"

So UserKeys is not a value you can run — it’s a type that means “one of these three exact strings.” A variable of type UserKeys is only allowed to hold "name", "email", or "age".

Indexed access reads a property’s type

The second tool looks like array indexing, but it works on types. User["email"] gives you the type of the email property, not its value.

type EmailType = User["email"]; // string
type AgeType = User["age"];     // number

You can even combine it with keyof to grab the type of every property at once:

type AnyValue = User[keyof User]; // string | number

That reads as “the type of any value you’d find under any key of User.” Hold on to these two ideas — keyof for the keys, indexed access for the value type. A mapped type is just the two of them working together inside a loop.

The shape of a mapped type

Here is the core pattern. Read it slowly:

type Example = {
  [K in keyof User]: User[K];
};

Let’s take it apart word by word:

  • [K in keyof User] is the loop. It says: for each key in User, let K stand for that key, one at a time.
  • : User[K] is what each key maps to. For the current key K, look up its type in User.

So this particular mapped type rebuilds User exactly as it was — every key keeps its original type. On its own that’s not useful, but it proves the mechanism: we visited every property and decided what it becomes. The power shows up the moment we change the right-hand side or add a modifier.

{ [K in keyof T]: <new type for K> }
   |        |          |
   |        |          +-- what this key becomes
   |        +------------- the source type we read from
   +---------------------- the loop variable (one key at a time)

K is just a name

The letter K is a convention, short for “key,” but it’s only a variable name. You could write [Prop in keyof User] and it would behave identically. Pick whatever reads clearly.

Transforming the values

Now let’s actually change something. Suppose you want a type where every field of User is a boolean — a common shape for tracking which fields a form has touched.

type TouchedFields = {
  [K in keyof User]: boolean;
};
// {
//   name: boolean;
//   email: boolean;
//   age: boolean;
// }

We kept the keys but threw away the original value types, replacing each with boolean. The structure of User carried over automatically. If someone later adds a phone: string field to User, TouchedFields gains a phone: boolean for free, because it’s generated, not copied.

To make this reusable for any type, wrap it in a generic. The T is a placeholder that gets filled in when you use the type:

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

type TouchedUser = AllBooleans<User>;

Now AllBooleans works on a product, an order, anything. This is exactly how TypeScript ships its own helpers, as we’ll see next.

Modifiers: adding and removing optional and readonly

Mapped types can do more than swap value types. They can add or remove two property modifiers: the ? that marks a property optional, and readonly that prevents reassignment.

To add a modifier, write it in front of the key. To remove one that’s already there, put a minus sign in front: -? or -readonly.

// Make every property optional
type Optional<T> = {
  [K in keyof T]?: T[K];
};

// Make every property required (strip the ?)
type Required<T> = {
  [K in keyof T]-?: T[K];
};

// Make every property read-only
type Frozen<T> = {
  readonly [K in keyof T]: T[K];
};

That ? after [K in keyof T] is the whole trick behind making fields optional. Combined with T[K] keeping the original value type, you get a faithful “everything optional” copy of any type.

You've already used these

If you’ve ever written Partial<User> or Readonly<User>, you’ve used mapped types without knowing it. They’re not special compiler keywords — they’re ordinary types defined in TypeScript’s standard library using exactly the pattern above.

How Partial and Readonly are actually built

Here’s the part that ties it together. The built-in utility types you reach for every day are short mapped types. Their real definitions look essentially like this:

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

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

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

Read each one against the pattern we built:

Utility What the mapped type does
Partial<T> Loop over keys, add ? so each is optional
Required<T> Loop over keys, use -? to strip optionality
Readonly<T> Loop over keys, add readonly to each

There’s no hidden mechanism. Once mapped types make sense, these definitions read like plain English: “for every key, keep its type, but mark it optional.” That’s the whole point of learning the pattern — it demystifies a big chunk of TypeScript’s standard library at once.

A worked example, step by step

Let’s build something slightly more interesting and walk through every line. Say we have a settings object, and we want a type where each field becomes a getter function that returns the original value type. So theme: string should turn into getTheme: () => string.

We need two things at once: change the value (wrap it in a function) and rename the key (add get and capitalize). Renaming uses an as clause after the loop, paired with a template literal type.

type Settings = {
  theme: string;
  fontSize: number;
  notifications: boolean;
};

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

type SettingsGetters = Getters<Settings>;
// {
//   getTheme: () => string;
//   getFontSize: () => number;
//   getNotifications: () => boolean;
// }

Now let’s unpack the dense middle line, [K in keyof T as ...]:

  1. [K in keyof T — the familiar loop: visit each key of T, one at a time, calling it K.
  2. as — start a key rename. Whatever follows becomes the new key name.
  3. `get${Capitalize<string & K>}` — a template literal type. It glues the text get in front of the key. Capitalize<...> uppercases the first letter, so theme becomes Theme. The string & K part is a small safety step that tells TypeScript to treat K as a string so Capitalize will accept it.
  4. : () => T[K] — the new value type. For the current key, the result is a function taking no arguments and returning the key’s original type, T[K].

Put together, each key like theme: string is rewritten into getTheme: () => string. The keys changed, the values changed, and yet we wrote the rule only once. Add a field to Settings and the matching getter appears automatically.

Key remapping is a modern feature

The as clause for renaming keys was added in a later TypeScript version than the basic mapped type. The plain [K in keyof T] form has been around much longer. If renaming keys gives you an unexpected error, check that your TypeScript version is recent enough rather than assuming the syntax is wrong.

When to reach for a mapped type

Mapped types shine when two copies of a type would otherwise have to stay in sync by hand. A few honest signals:

  • You keep writing near-identical types that differ only by ?, readonly, or a uniform value type.
  • You want a type to update itself automatically when its source gains or loses fields.
  • A library asks for “the same shape, but all optional” or “the same keys, but mapped to handlers.”

And a word of restraint: if you only need one fixed type and it won’t change, just write it out plainly. A mapped type earns its keep through reuse and through staying synced with a source type. Reaching for the clever version when a simple object type would do only makes the code harder to read for the next person.

Recap

Mapped types are one of those features that look intimidating and turn out to be small once you see the parts. Here’s the whole idea in a few lines:

  • keyof T gives you the union of a type’s keys, as a type.
  • T[K] (indexed access) gives you the type of a property.
  • A mapped type, { [K in keyof T]: ... }, loops over those keys and decides what each one becomes.
  • Modifiers ? and readonly can be added directly, or removed with -? and -readonly.
  • TypeScript’s own Partial, Required, and Readonly are just these mapped types, nothing more.
  • An as clause with a template literal type lets you rename keys while you transform them.

The next time you see Partial<User> in a codebase, you’ll know exactly what’s happening under the hood: a tiny loop, visiting each key, adding a question mark. Start with the built-in utilities, then write your own the day you find yourself copying a type and editing it by hand. That’s the moment a mapped type pays for itself.

Tags:typescriptmapped-typeskeyofutility-typesfrontend
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

Frontend best practices overview cover
Frontend / Fundamentals

Frontend Best Practices: An Overview

A friendly map of what good frontend really means — semantic HTML, clean CSS, unobtrusive JavaScript, responsive layouts, performance, and progressive enhancement, with pointers to go deeper.

Sep 20, 20268 min read
A Frontend Developer Roadmap cover with the path HTML to CSS to JavaScript
Frontend / Fundamentals

A Frontend Developer Roadmap: What to Learn, and in What Order

A calm, step-by-step learning path for beginners: HTML, CSS, JavaScript, developer tools, responsive and accessible design, a framework, TypeScript, and deployment, with the reasoning behind each step.

Sep 20, 202611 min read
Title card reading Styling and Interactivity over a dark blue ACY Partner background
Frontend / Fundamentals

How Styling and Interactivity Work

A beginner-friendly look at how CSS turns plain HTML into a designed layout, and how JavaScript adds behavior — the structure, style, behavior mental model for building web pages.

Sep 20, 20269 min read
Three front-end layers combining on one web page
Frontend / Fundamentals

How HTML, CSS, and JavaScript Work Together

A hands-on walkthrough for beginners: build one small button, give it structure with HTML, looks with CSS, and behavior with JavaScript, and watch the three layers cooperate on a real page.

Sep 20, 20268 min read
The Frontend Developer Toolkit cover with editor, browser, and CLI motifs
Frontend / Fundamentals

The Frontend Developer Toolkit

A friendly tour of the everyday tools a frontend developer relies on — code editor, browser and DevTools, terminal, version control, package managers, and a dev server — and what each one is actually for.

Sep 20, 202610 min read
Cover illustration for What Frontend Developers Do
Frontend / Fundamentals

What Frontend Developers Do: A Beginner's Guide to the Role

A clear, beginner-friendly look at what frontend developers actually do every day: turning designs into working interfaces, building reusable UI, and caring about responsiveness, accessibility, and speed.

Sep 20, 202610 min read