Skip to content

Mapped Types in TypeScript: Transforming Every Property

Aman Kumar Singh5 min read
Part 7 of 9From the TypeScript Deep Dive series
Mapped Types in TypeScript: Transforming Every Property — article by Aman Kumar Singh

Mapped types are how you transform every property of a type at once. If conditional types are the type system's branching, mapped types are its iteration: a way to walk over the keys of a type and produce a new property for each one. They are the machinery behind Partial, Readonly, Record, and most of the other utility types, and once you can read and write them, "I need a version of this type but with every field changed somehow" stops being a copy-paste chore and becomes a one-line transformation.

This is part of the TypeScript type system series. It builds on generics and the keyof operator.

keyof and indexed access, first

Two small pieces make mapped types possible, and they are useful on their own. keyof takes a type and gives you the union of its property names:

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

And indexed access, T[K], gives you the type of a property by its key:

type NameType = User["name"] // string

Together they let you talk about "the keys of this type" and "the value type at this key" — which is exactly what you need to iterate over a type and rebuild it.

The mapped type syntax

A mapped type uses the [K in Keys] syntax to say "for each key K in this set of keys, produce a property." It looks like a for loop expressed in type syntax:

type Stringify<T> = {
  [K in keyof T]: string
}

interface User { id: number; name: string; active: boolean }
type StringUser = Stringify<User>
// { id: string; name: string; active: string }

Read [K in keyof T] as "for each key K in T." For each one, this mapped type declares a property named K whose value type is string. The result is a new type with all of User's keys but every value turned to string. Change the value type on the right and you change what each property becomes. Reference T[K] on the right and you can keep or transform the original value type instead of replacing it — which is where mapped types get genuinely useful.

Modifiers: optional and readonly

Mapped types can add or remove the ? (optional) and readonly modifiers as they go, and this is what most of the built-in utilities do. Partial adds optional to every property:

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

The ? after the bracket makes each generated property optional, and T[K] preserves each original value type. That is the entire definition of Partial — no magic, just a mapped type with an added modifier. Readonly is the same idea with readonly in front:

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

You can also remove modifiers with a minus sign, which is how Required strips optionality: [K in keyof T]-?: T[K] removes the ? from every property. Seeing that these core utilities are three-line mapped types is the moment the type system stops feeling like a fixed set of built-ins and starts feeling like something you can extend yourself.

Transforming value types with T[K]

The real work happens when you combine the iteration with a transformation of each value type. Say you want a type where every property of an object becomes a getter function returning that property's type:

type Getters<T> = {
  [K in keyof T]: () => T[K]
}

interface User { id: string; age: number }
type UserGetters = Getters<User>
// { id: () => string; age: () => number }

Each property is rebuilt as a function type that returns the original value type, captured through T[K]. This pattern — iterate the keys, do something to each value type — is the core of most real mapped types, from wrapping every field in a Promise to making a deeply readonly version of a config object.

Key remapping with as

A more recent addition lets you transform the keys themselves, not just the values, using an as clause in the mapped type. This is powerful for generating derived shapes where the property names change:

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

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

The as clause rewrites each key — here using a template literal type to prefix get and capitalize the original name. Key remapping also lets you filter keys: if the as expression produces never for a key, that property is dropped from the result, which is how you build "all the string-valued properties of T" and similar selective transformations. It is the most advanced corner of mapped types, and you will not need it often, but it is the tool when you genuinely need to derive one shape's property names from another's.

Where this fits

Mapped types are the iteration half of type-level programming, paired with conditional types for the branching half. Most application code uses them indirectly, through the utility types built on top of them, and that is exactly as it should be — you should reach for Partial and Readonly far more often than you write a mapped type by hand. But when you hit a case the built-ins do not cover — every field wrapped, every key renamed, a whole shape derived from another — knowing how to write the mapped type yourself is what keeps you from falling back to any and losing the type safety at precisely the point a complex transformation makes it most valuable. Like conditional types, the skill is knowing the tool exists and reaching for it deliberately, not reaching for it everywhere.

Key takeaways

  • keyof gives the union of a type's property names, and T[K] (indexed access) gives the value type at a key — the two pieces mapped types build on.
  • The [K in keyof T] syntax iterates over a type's keys, producing a new property for each — a for loop expressed in types.
  • Mapped types add or remove the ? and readonly modifiers, which is exactly how Partial, Readonly, and Required are defined.
  • Reference T[K] on the right to keep or transform each original value type — wrap each field in a function, a Promise, and so on.
  • Key remapping with an as clause rewrites or filters keys (producing never drops a key), enabling derived shapes with changed property names.

Frequently asked questions

What is a mapped type in TypeScript?

A type that iterates over the keys of another type and produces a new property for each, using the [K in keyof T] syntax. It is the type-level equivalent of a loop, and it is how utility types like Partial, Readonly, and Record are implemented.

How do Partial and Readonly work internally?

Both are one-line mapped types. Partial is { [K in keyof T]?: T[K] }, adding the optional modifier to every property. Readonly is { readonly [K in keyof T]: T[K] }. The -? modifier removes optionality, which is how Required strips it.

What is key remapping in mapped types?

An as clause that transforms the keys themselves, not just the values — for example [K in keyof T as `get${Capitalize<string & K>}`] to prefix and capitalize each key. If the as expression yields never for a key, that property is dropped, which lets you filter keys.

Should I write mapped types by hand often?

No. Most code should use the built-in utility types that are built on mapped types. Write one yourself only when the built-ins do not cover the case — wrapping every field, renaming every key, deriving a whole shape — where it keeps you from falling back to any.

Related articles

The satisfies Operator in TypeScript: Check Without Widening — Aman Kumar Singh
TypeScript Type Narrowing and Type Guards — Aman Kumar Singh
Discriminated Unions in TypeScript: Making Illegal States Impossible — Aman Kumar Singh

Explore more on

Aman Kumar Singh

About the author

I'm Aman Kumar Singh, a software engineer in Noida, India building scalable full-stack products with React, Next.js, Node.js, NestJS, PostgreSQL, Redis, and AWS. I write about backend engineering, distributed systems, and system design.