Skip to content
Mapped Types and Utility Types
step 1/7

Reading — step 1 of 7

Learn

~3 min readThe Type System Toolkit

Mapped types transform every property of an existing type. They power TypeScript's built-in utility types and let you build custom ones for your domain.

Basic syntax

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

interface User { name: string; age: number }
type LockedUser = ReadonlyAll<User>;
// { readonly name: string; readonly age: number }

[K in keyof T] iterates over each property name. T[K] is the indexed-access type (the value type at key K).

The built-in utility types

TypeScript ships these — used constantly in real code:

interface User {
    name: string;
    age: number;
    email: string;
}

// Make all fields optional:
type UserUpdate = Partial<User>;
// { name?: string; age?: number; email?: string }

// Make all fields required (removes ?):
type FullUser = Required<UserUpdate>;

// Make all fields readonly:
type FrozenUser = Readonly<User>;

// Pick specific keys:
type UserContact = Pick<User, 'name' | 'email'>;
// { name: string; email: string }

// Omit specific keys:
type PublicUser = Omit<User, 'email'>;
// { name: string; age: number }

// Build a record (uniform values keyed by some union):
type Permissions = Record<'read' | 'write' | 'admin', boolean>;
// { read: boolean; write: boolean; admin: boolean }

Partial, Required, Readonly, Pick, Omit, Record — memorize these. They appear in nearly every TypeScript codebase.

Modifier control: + and -

Mapped types can ADD or REMOVE the readonly and ? modifiers explicitly:

type Mutable<T> = {
    -readonly [K in keyof T]: T[K];     // remove readonly
};

type Required2<T> = {
    [K in keyof T]-?: T[K];              // remove optional (?) modifier
};

That's how Required<T> strips the ? from every property.

Key remapping (TypeScript 4.1+)

Transform the keys themselves:

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

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

Filter properties by mapping unwanted keys to never:

type StringProps<T> = {
    [K in keyof T as T[K] extends string ? K : never]: T[K];
};

type OnlyStrings = StringProps<User>;
// { name: string; email: string }

This is the canonical pattern for filtering properties by their value types.

Building your own utilities

Real-world example — a type that requires AT LEAST one of N options:

type RequireAtLeastOne<T> = {
    [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
}[keyof T];

type Login = RequireAtLeastOne<{ email?: string; phone?: string; username?: string }>;
// Object must have AT LEAST one of: email, phone, username

Reads like a knot, but composes the primitives — Pick, Omit, Partial, Required, mapped types — to express a precise constraint.

Common patterns

// Pull all readonly properties (excluding methods):
type ReadonlyKeys<T> = {
    [K in keyof T]-?: <U>() => U extends { [P in K]: T[K] } ? 1 : 2 extends
                       <U>() => U extends { -readonly [P in K]: T[K] } ? 1 : 2
                       ? K
                       : never;
}[keyof T];

// DeepPartial — recursive Partial:
type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

The [keyof T] at the end of those patterns extracts the union of values from the mapped type — a common trick for collecting matching keys.

Common mistakes

  • Using Partial deeply when you mean DeepPartialPartial<{ a: { b: string } }> is { a?: { b: string } }, not { a?: { b?: string } }.
  • Confusing Pick and Extract — Pick takes object + keys; Extract takes union + filter type.
  • Forgetting key remapping needs as[K in keyof T as NewKey] is the syntax.
  • Hidden string & casts on key remappingK is keyof T, often string | number | symbol. Capitalize and other string utilities require string & K.
  • Recursive mapped types hitting recursion limit — TypeScript caps depth (~50 since 4.7). Iterative versions via tuple length workaround can extend that.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…