Master Mapped Types in TypeScript
The Secret to Dynamic Type Transformations

Software Engineer and a tech enthusiast. Node.js | JavaScript | Next.js Don't miss my YouTube videos 💌 Open to collaborations, tech discussions, and sharing the love for all things tech. Check my shop: https://www.redbubble.com/people/moucodes
TL;DR
Mapped types let you transform existing types into new shapes dynamically, without repeating yourself. Mastering them unlocks flexible, DRY, and safer TypeScript code patterns you’ll use everywhere.
Why Mapped Types Deserve Your Attention
When I first stumbled on mapped types, it felt like magic. I had been writing the same verbose type definitions again and again, until I realized TypeScript could do the work for me.
Mapped types let you take an existing type and transform it systematically, changing its keys, altering its modifiers, or reshaping it entirely. Think of it like SQL for types: you query, project, and reshape, but at the type level.
And the best part? Your editor will thank you with autocomplete and type safety for free.
The Basics: What Are Mapped Types?
Mapped types let you create new types by iterating over the keys of another type. You’ll usually see them paired with keyof and conditional types.
Here’s the canonical example:
type Readonly<T> = {
readonly [K in keyof T]: T[K];
}
type User = {
id: number;
name: string;
active: boolean;
}
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: 1,
name: "John",
active: true
};
user.active = false; // Error: Cannot assign to 'active' because it is a read-only property.
We “mapped” over each property of User, added readonly, and produced a new type. That’s the power of mapped types in action.
Transforming Properties Dynamically
You don’t have to stop at adding readonly. You can dynamically transform keys, values, or modifiers.
Example 1: Making all fields optional
type Partial<T> = {
[K in keyof T]?: T[K];
};
type Config = {
host: string;
port: number;
}
type PartialConfig = Partial<Config>;
/*
{
host?: string | undefined;
port?: number | undefined;
}
*/
This is essentially how TypeScript’s built-in Partial<T> works.
Example 2: Changing Value Types
What if you want all values to become string?
type Stringify<T> = {
[K in keyof T]: string;
};
type EnvVars = {
NODE_ENV: "production" | "development";
PORT: number;
};
type StringifiedEnv = Stringify<EnvVars>;
/*
{
NODE_ENV: string;
PORT: string;
}
*/
This is surprisingly useful for things like environment configs, where the actual runtime values are strings.
Example 3: Key Remapping with as
One of the newer TypeScript features is key remapping inside mapped types. This lets you rename keys dynamically.
type PrefixKeys<T, P extends string> = {
[K in keyof T as `${P}${Capitalize<string & K>}`]: T[K]
}
type Options = {
dark: boolean;
compact: boolean;
}
type PrefixedOptions = PrefixKeys<Options, "is">;
/*
{
isDark: boolean;
isCompact: boolean;
}
*/
That’s some real power: not only mapping but also reshaping the keys themselves.
Practical Use Cases
1. API Response Normalization
If you’re consuming APIs where all fields are snake_case, but you want camelCase in your app:
type SnakeToCamel<T> = {
[K in keyof T as K extends `${infer F}_${infer R}` ? `${F}${Capitalize<R>}`
: K]: T[K];
}
type ApiUser = {
first_name: string;
last_name: string;
}
type User = SnakeToCamel<ApiUser>;
/*
{
firstName: string;
lastName: string;
}
*/
2. Role-Based Access Control
You can generate permissions from a role definition automatically:
type Actions<T> = {
[K in keyof T as `can${Capitalize<string & K>}`]: boolean;
}
type Features = {
edit: {};
delete: {};
view: {};
};
type Permissions = Actions<Features>;
/*
{
canEdit: boolean;
canDelete: boolean;
canView: boolean;
}
*/
3. Enforcing Client-Side Forms
Transform backend types into “form-ready” types where everything becomes optional and string-based:
type Formify<T> = {
[K in keyof T]?: string;
};
type Product = {
id: number;
name: string;
price: number;
};
type ProductForm = Formify<Product>;
/*
{
id?: string;
name?: string;
price?: string;
}
*/
Gotchas & Tips
Watch out for unions: Mapped types over union keys can sometimes produce unexpected distributive behavior. Wrap with
keyof T & stringto keep things tidy.Don’t overcomplicate: It’s tempting to go wild with conditional and template literal types. Keep it readable.
Modifiers stack: You can combine
readonlyand?in the same mapped type. Useful, but don’t create awkward types.Runtime mismatch: Remember mapped types only exist at compile time. You still need runtime validation (e.g.,
zod) if dealing with untrusted data.
Conclusion
Mapped types are a powerful way to simplify and streamline your TypeScript code. Start by refactoring verbose type definitions, study how built-in utility types like Partial, Required, Pick, and Record are implemented, and then gradually explore advanced patterns using template literal and conditional types. Mastering these concepts will help you write cleaner, more flexible, and maintainable type definitions.
Enjoyed this article? Give it a share and help someone else level up their coding skills!
Don’t stop here, explore my other TypeScript and developer-focused articles, and follow me for more.




