Skip to content
Skip to content
DocsTypeScript ExpertexpertMapped Types
Chapter 4 of 10·expert·9 min read

Mapped Types

Kiểu Ánh Xạ

Transform existing types by iterating over their keys

Hover or tap any paragraph to see Vietnamese translation

Basic Syntax

A mapped type creates a new type by iterating over the keys of an existing type and transforming each one. It is the mechanism behind TypeScript's most commonly used utility types.

basic-mapped.ts
1// Basic form: iterate over all keys of T and keep their types2type Copy<T> = {3	[K in keyof T]: T[K];4};56type User = { name: string; age: number; active: boolean };7type UserCopy = Copy<User>;8// { name: string; age: number; active: boolean }910// Transform the value type for every key11type Stringify<T> = {12	[K in keyof T]: string;

The [K in keyof T] syntax iterates over every key of T. Within the mapping, K refers to the current key and T[K] is the property's type in the original type.

Modifiers: readonly and ?

Mapped types can add or remove the readonly and optional (?) modifiers from each property. Prefix + adds a modifier (the default), prefix - removes it.

modifiers.ts
1type User = { name: string; age?: number; readonly id: string };23// Add readonly to every property (same as built-in Readonly<T>)4type Readonly<T> = {5	readonly [K in keyof T]: T[K];6};78// Add ? to every property (same as built-in Partial<T>)9type Partial<T> = {10	[K in keyof T]?: T[K];11};12
Info
The - prefix is the only way to strip readonly or ? from a property. Without it, adding the modifier when one already exists is a no-op rather than an error.

Key Remapping with as

TypeScript 4.1 introduced key remapping via an as clause. This lets you transform key names — not just value types — inside a mapped type.

key-remapping.ts
1// Prefix every key with "get" and capitalize it2type Getters<T> = {3	[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];4};56type User = { name: string; age: number };7type UserGetters = Getters<User>;8// { getName: () => string; getAge: () => number }910// Suffix every key with "Input" for form state11type FormInputs<T> = {12	[K in keyof T as `${string & K}Input`]: string;

Filtering Keys with never

Returning never from the as clause removes that key from the resulting type. This is the idiomatic way to conditionally exclude properties inside a mapped type.

filtering-keys.ts
1// Keep only string-valued properties2type StringProps<T> = {3	[K in keyof T as T[K] extends string ? K : never]: T[K];4};56type Mixed = { name: string; age: number; bio: string; active: boolean };78type OnlyStrings = StringProps<Mixed>;9// { name: string; bio: string }1011// Keep only method (function) properties12type Methods<T> = {

Transforming Values with Conditional Types

Mapped types and conditional types compose naturally. Use a conditional type on the value side to transform each property's type based on its own shape.

value-transforms.ts
1// Wrap every property in a Promise2type Promisify<T> = {3	[K in keyof T]: Promise<T[K]>;4};56// Unwrap Promise from every property7type Awaited<T> = {8	[K in keyof T]: T[K] extends Promise<infer R> ? R : T[K];9};1011// Make nullable properties optional12type NullableToOptional<T> = {

Practical Patterns

Here are production-grade mapped type patterns found in large TypeScript codebases — from API normalisation to form schema generation.

practical-patterns.ts
1// Record<K, V> — maps a set of keys to a value type2type Record<K extends string | number | symbol, V> = {3	[P in K]: V;4};5type UserIndex = Record<string, { name: string }>;67// Pick<T, K> — keep only the specified keys8type Pick<T, K extends keyof T> = {9	[P in K]: T[P];10};11type UserPreview = Pick<User, "name" | "id">;12

Recursive Mapped Types

TypeScript supports recursive mapped types, letting you apply transformations to every level of a nested object type. Always guard with a conditional type to stop recursion at non-object leaves.

recursive-mapped.ts
1// Deep readonly — every nested object is also frozen2type DeepReadonly<T> = {3	readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];4};56// Deep required — remove optional from every level7type DeepRequired<T> = {8	[K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K];9};1011// Deep record — every leaf value becomes a given type12type DeepRecord<T, V> = {
Info
Recursive mapped types applied to circular types cause infinite expansion. TypeScript detects some cycles but not all — test carefully with self-referential structures like linked lists or tree nodes.

Summary

  • { [K in keyof T]: T[K] } is the base form — iterate every key and preserve its type
  • + adds and - removes readonly and ? modifiers per property
  • The as clause remaps key names, commonly with template literal types
  • Return never in the as clause to filter out unwanted keys
  • Readonly, Partial, Required, Pick, Omit, Record are all mapped types
  • Combine with conditional types on the value side for per-property conditional transformation
  • Recursive mapped types propagate transformations through every level of a nested object
  • Built: 6/25/2026, 3:03:36 PM