Introduction to Mapped Types: Creating New Types from Old Ones
Mapped types are one of TypeScript's most powerful tools for transforming existing types into new, reusable shapes. They let you programmatically iterate over the properties of a type and produce a new type, enabling patterns like making every property optional, readonly, or selectively remapping property keys. For intermediate developers, mapped types are a gateway to more expressive, maintainable, and DRY type code.
In this tutorial you will learn why mapped types matter, how to create basic and advanced mapped types, and how to combine them with conditional types, utility types, and generics to solve common problems in real code. We'll walk through practical examples, step-by-step instructions, and troubleshooting tips so you can apply each technique confidently in your codebase.
By the end of this article you will be able to:
- Read and write basic mapped types
- Apply modifiers like optional and readonly programmatically
- Combine mapped types with keyof, generics, and conditional types
- Build practical utilities such as deep readonly, selective pick/omit, and transformation pipelines
- Avoid common pitfalls and optimize type performance
This article is written for intermediate TypeScript developers who already know the basics of types, interfaces, and generics. If you need a refresher on utility types or generics, check our overview on Introduction to Utility Types: Transforming Existing Types and Introduction to Generics: Writing Reusable Code before diving in.
Background & Context
Mapped types build on two core TypeScript features: the keyof operator and index signatures in type position. The basic idea is simple: take a union of keys and produce a type by iterating those keys. This avoids repeating similar patterns for every property and helps keep large type definitions consistent when the shape changes.
Mapped types are the foundation behind many of TypeScript's built-in utility types such as Partial, Readonly, Pick, and Record. Understanding how mapped types work will let you recreate or extend those utilities, and build domain-specific type transformations that match your application's invariants.
You will also see how mapped types interact with other features like conditional types and type narrowing. If you want more on narrowing techniques, our guides on Type Narrowing with typeof Checks in TypeScript and Type Narrowing with instanceof Checks in TypeScript can be helpful context.
Key Takeaways
- Mapped types let you programmatically transform properties of a type using a key union.
- You can add or remove modifiers like optional and readonly inside a mapped type.
- Combine mapped types with keyof, generics, and conditional types for powerful, reusable utilities.
- Many built-in utility types are implemented with mapped types; learn to extend them.
- Watch for distributive conditional types and excessive computation in large types to maintain compile performance.
Prerequisites & Setup
Before following the examples, make sure you have:
- TypeScript 4.x or later installed (many mapped type features improve across versions).
- Familiarity with basic types, interfaces, and the keyof operator.
- A simple project scaffold or TypeScript playground where you can test types interactively.
To install TypeScript locally in a node project if needed:
npm install --save-dev typescript npx tsc --init
If you are less confident with utility types, you may want to read our practical guide on Using Partial
Main Tutorial Sections
1) Basic mapped type syntax
A minimal mapped type uses square brackets with a key union and produces a new property mapping. Example:
type Keys = 'a' | 'b' | 'c'
type M = { [K in Keys]: number }
// equivalent to { a: number; b: number; c: number }Here K iterates over every member of Keys and emits a property with that name and type number. Mapped types are purely compile-time constructs and produce no runtime output. Use them when you want to ensure a certain transformation applies consistently to every key in a set.
2) Recreating built-in utilities: Partial and Readonly
Partial and Readonly are trivial mapped types that add modifiers. For example, Partial
type MyPartial<T> = { [K in keyof T]?: T[K] }The optional modifier ? after the bracket makes each property optional. Readonly is similar:
type MyReadonly<T> = { readonly [K in keyof T]: T[K] }Understanding these simple definitions helps when you need to customize behavior, for example to make only some properties optional, or to apply readonly only to nested properties.
For a deeper look at Partial and Readonly utilities and practical tips, see the reference on Using Partial
3) Using the mapping modifiers + and -
TypeScript provides plus and minus modifiers to add or remove modifiers inside mapped types. Example:
type T1<T> = { -readonly [K in keyof T]: T[K] } // remove readonly
type T2<T> = { +readonly [K in keyof T]: T[K] } // ensure readonlyCombined with optionality:
type MakeOptional<T> = { [K in keyof T]?: T[K] }
type MakeRequired<T> = { [K in keyof T]-?: T[K] }These modifiers let you explicitly control property modifiers when composing utilities. This technique is useful when writing functions that accept a mix of mutable and immutable shapes.
4) Mapping keys using 'as' and key remapping
Key remapping appeared in newer TypeScript versions and lets you transform the key names themselves using the as clause.
type PrefixKeys<T, P extends string> = { [K in keyof T as `${P}${Extract<K, string>}`]: T[K] }
type User = { id: number; name: string }
type Prefixed = PrefixKeys<User, 'user_'>
// { user_id: number; user_name: string }This is a powerful feature for adapting shapes to APIs that require different prefixes, or when generating DTOs. Template literal types in the key remap can produce readable, consistent keys programmatically.
For advanced extraction techniques and type manipulation, you might also find our guide on Deep Dive: Using Extract<T, U> to Extract Types from Unions helpful.
5) Index signatures, keyof, and generic keys
Mapped types are usually driven by keyof T, which produces a union of keys. Use generic constraints to ensure mapped keys are valid:
type MapValues<T, U> = { [K in keyof T]: U }
// Usage
type Point = { x: number; y: number }
type PointStrings = MapValues<Point, string>
// { x: string; y: string }If you want to map over a specific subset of keys, create a key union explicitly using Extract or Exclude. See our guide on Using Exclude<T, U>: Excluding Types from a Union for patterns that work well with mapped types.
6) Conditional types inside mapped types
Combining mapped types with conditional types unlocks conditional property transformations. For example you can convert function properties to return types or filter by property types:
type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>This pattern computes a union of keys that are functions and then uses Pick to build a sub-type. You can combine these ideas to build complex shape transformations. For Pick and Omit style patterns, check Using Pick<T, K>: Selecting a Subset of Properties and Using Omit<T, K>: Excluding Properties from a Type.
7) Deep mapped types and recursive transforms
Sometimes you need to transform deeply nested structures, like making every nested property readonly. You can implement a recursive mapped type with conditional checks:
type DeepReadonly<T> = T extends Function
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: TThis handles functions specially and recurses into objects. Deep mapped types can be expensive for the compiler for very large types, so consider limiting recursion depth or using explicit DTOs when performance matters.
For related utilities that exclude nullish values, see Using NonNullable
8) Mapping unions and distributive behavior
Conditional types distribute over unions by default, which affects mapped types when you combine them with conditional transforms. Consider this example:
type ToArray<T> = T extends any ? T[] : never
// ToArray<'a' | 'b'> => string[]? Actually => ('a'[] | 'b'[])When designing mapped type utilities, understand when distribution helps and when it creates unwanted unions. Use wrappers like [T] to prevent distribution when needed.
Also, mapping over a union of keys translates into a property set; ensure the individual key transformations are compatible to avoid producing broader unions for property types.
9) Practical example: Transform an API response into a client model
Suppose an API returns snake_case keys and you want camelCase in your client types. Use key remapping and mapped transformations:
type ApiToClient<T> = { [K in keyof T as CamelCaseString<Extract<K, string>>]: T[K] }
// CamelCaseString would be a template literal type that converts snake_case to camelCaseImplementing CamelCaseString is an exercise in template literal types and recursive mapped templates. This pattern keeps runtime converters consistent with compile-time shapes and reduces bugs where keys are renamed manually.
10) Combining mapped types with generics and constraints
Generics let mapped types remain flexible. Use constraints to prevent invalid operations on unknown shapes:
type SafeMap<T extends object, U> = { [K in keyof T]: U }If you need to map only writable keys, you can compute them and map accordingly. For more on generics and how to limit type possibilities, read our tutorials on Generic Functions: Typing Functions with Type Variables and Constraints in Generics: Limiting Type Possibilities.
Advanced Techniques
Once you master the basics, you can compose mapped types with other advanced features. Use key remapping plus template literal types to build domain-specific naming conventions, or pair mapped types with conditional extraction utilities like Extract and Exclude to compute finely targeted subsets. Another tactic is using sentinel types or brand patterns inside mapped types to preserve opaque types while transforming wrappers.
To reduce compiler work, limit the scope of mapped type transformations with narrower key unions instead of mapping over keyof T when T is large. You can also memoize common type computations by exposing them as named utility types rather than inlining long conditional logic everywhere.
If you work with runtime validation, keep type-level transforms aligned with runtime mappers to reduce mismatch risks. Tools like io-ts or zod benefit from a consistent approach of generating runtime schemas alongside mapped-type-driven static types.
Best Practices & Common Pitfalls
Dos:
- Prefer explicit key unions when possible to reduce compile-time complexity.
- Reuse named utility types for common transforms to improve readability.
- Comment complex mapped type logic so future maintainers understand intent.
Don'ts:
- Avoid over-recursive mapped types applied to huge deeply nested models; they can slow down the compiler.
- Don't rely on mapped types for runtime behavior; they exist only at compile time and must be paired with runtime converters if needed.
- Be careful with distributive conditional types unintentionally widening unions. Wrap types to prevent distribution when necessary.
Troubleshooting tips:
- Use TypeScript's quick info in your editor to inspect intermediate types.
- Break large transforms into smaller named types to isolate the source of type errors.
- If the compiler is slow, try narrowing the input type or upgrading TypeScript to a newer version that improves mapped type performance.
For more on type assertions and their risks when bridging compile-time and runtime, see Type Assertions (as keyword or <>) and Their Risks.
Real-World Applications
Mapped types are useful in many scenarios:
- Building SDKs or client APIs that need consistent, transformed types from server responses.
- Creating configuration systems where default values are applied and some properties are optional or readonly.
- Defining domain model adapters that map storage shapes to business logic shapes.
For example, converting a database row type to an application model often involves renaming keys, converting nullable fields into optional properties using NonNullable, and making certain fields readonly for safety. See Using NonNullable
Conclusion & Next Steps
Mapped types unlock a higher level of type expressiveness in TypeScript. Start by re-implementing simple utilities like Partial and Readonly, then progress to key remapping and conditional transforms. Combine these techniques with generics and utility types to build robust, reusable type tooling for your codebase.
Next steps: practice by rewriting small utility types in your projects, and explore advanced patterns such as deep mapped types and key remapping. Revisit our posts on Introduction to Utility Types: Transforming Existing Types and Generic Interfaces: Creating Flexible Type Definitions to continue building your skill set.
Enhanced FAQ
Q1: What is the simplest way to make every property in a type optional using a mapped type? A1: Use the optional modifier ? inside a mapped type over keyof T. Example:
type MyPartial<T> = { [K in keyof T]?: T[K] }This mirrors the built-in Partial
Q2: How do I rename keys in a mapped type? A2: Use the as clause with a key remapping expression. For example, to add a prefix:
type PrefixKeys<T, P extends string> = { [K in keyof T as `${P}${Extract<K, string>}`]: T[K] }Template literal types let you create many string transformations for key names.
Q3: Can mapped types access the original type value when remapping keys? A3: Yes. Inside the mapped type you can return T[K] for the property type, based on the original property. The as clause is only for renaming keys; it does not change how you access the original property type.
Q4: Are mapped types runtime constructs? A4: No. Mapped types are entirely erased at runtime. If your transformation requires runtime behavior (like renaming keys in JSON), you must write a runtime mapper function that matches the compile-time mapped type.
Q5: How do I make only a subset of properties optional? A5: Construct a union of the keys you want optional and combine mapped types with intersection or utility types. Example:
type Optionalize<T, K extends keyof T> = Omit<T, K> & { [P in K]?: T[P] }This keeps other keys unchanged and makes only K optional.
Q6: What about deep transforms? Are there performance concerns? A6: Deep recursive mapped types like DeepReadonly are powerful, but they can create heavy compile-time computation for big types. If you hit slowdowns, consider limiting depth, converting some structures to explicit types, or upgrading TypeScript which often brings performance improvements.
Q7: How do conditional types interact with mapped types? A7: Conditional types allow you to change property types based on conditions. Combined with mapped types you can filter or transform properties conditionally. Watch out for distributive behavior on unions in conditional types; wrap types with square brackets to suppress distribution if you need a different result.
Q8: Can I map over numeric keys or symbol keys? A8: Yes. keyof T may include string, number, or symbol unions. When using template literal remapping you are restricted to string-like conversions; use Extract<K, string> when operating on key strings. For symbols, remapping is less common; be careful because template literal keys only make sense for strings.
Q9: How do Pick and Omit relate to mapped types? A9: Both Pick and Omit are implemented with mapped type concepts. Pick<T, K> iterates over K and selects T[K]. Omit uses Exclude to drop keys then maps the remainder. For concrete patterns, review Using Pick<T, K>: Selecting a Subset of Properties and Using Omit<T, K>: Excluding Properties from a Type.
Q10: Where should I go next to deepen my knowledge of types? A10: Practice combining mapped types with generics and explore utility types like Extract and Exclude to refine key unions. Our tutorials on Deep Dive: Using Extract<T, U> to Extract Types from Unions and Generic Functions: Typing Functions with Type Variables are good next steps. Also, explore type narrowing techniques like Type Narrowing with the in Operator in TypeScript to better integrate runtime checks with your compile-time mapped types.
