Mapped Types
Changing object shapes without rewriting them from scratch
If you have ever copied an object type in TypeScript just to tweak it a little you have done more work than you needed. Mapped types let you transform existing types instead of starting over. Think of it like running a stencil over an existing blueprint and adjusting only the parts you want.
Why this matters
When your codebase grows you will often need variants of the same type. One with all fields optional for updates. One where all values are read only. One where certain properties change type. Without mapped types you would either duplicate definitions or use any
and lose safety. Mapped types give you a way to apply changes across a type automatically while keeping everything checked at compile time.
The core idea
A mapped type is a way to loop over the keys of another type and create a new type from them. You use the keyof
operator to get the keys and the in
keyword to map over them. This is all done at the type level so it does not affect runtime performance.
Example
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean
}
type Features = {
darkMode: () => void
beta: () => void
}
type FeatureFlags = OptionsFlags<Features>
// { darkMode: boolean; beta: boolean; }
Here OptionsFlags
takes any type and replaces every property value with boolean
.
Three practical moves
-
Make everything optional
Instead of manually adding
?
to every property you can use a mapped type.type Partial<T> = { [P in keyof T]?: T[P] }
This is actually how the built in
Partial
utility works. Use it when you have an update form or patch API that should accept only part of a type. -
Make everything read only
Prevent accidental mutation across an object's properties.
type Readonly<T> = { readonly [P in keyof T]: T[P] }
Ideal for config objects or constants you want to protect from changes.
-
Change all property types
You can transform the value type while keeping the same keys.
type Nullable<T> = { [P in keyof T]: T[P] | null }
Handy when dealing with API responses where fields might be null.
Contrast with writing types manually
Without mapped types you would write seprate definitions for every variation. This is repetitive and error prone. A mapped type guarantees that if the original type changes your derived type changes too. No stale definitions. It is the difference between tracing a drawing each time and just changing the stencil once.
A micro utility you can reuse
type WithPrefix<T, Prefix extends string> = {
[K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K]
}
This takes an object type and renames its keys with a given prefix while keeping the values the same. Great for creating namespaced settings or environment variable shapes.
Wrap up
Mapped types are one of those TypeScript features that pay off more the larger your project becomes. They reduce duplication and keep types in sync as your domain evolves. Start by using the built in ones like Partial
and Readonly
then try building your own small utilities. It is a small shift in thinking but it makes your type definitions far more flexible and maintainable. If you have a favorite mapped type trick I would love to hear it.
Stay Updated
Get the latest TypeScript tips, tutorials, and course updates delivered straight to your inbox.