Advanced1 questionFull Guide

TypeScript Mapped Types — Complete Interview Guide

Master TypeScript's mapped types — how they iterate over keys to transform type shapes, modifier addition and removal, key remapping with as, and how every built-in utility type (Partial, Required, Readonly, Pick) is implemented with them.

The Mental Model

Mapped types are TypeScript's for-loop for type definitions. Just as Array.map() transforms each element of an array into a new element, a mapped type iterates over each key in a type and transforms it into a new property — possibly changing the key name, the value type, or adding/removing modifiers like optional (?) and readonly. The result is a completely new type derived from the original, keeping the two in sync automatically: change the source type and every mapped type built from it updates without any manual edits.

The Explanation

Basic Mapped Type Syntax

// The fundamental pattern: [P in keyof T]: SomeType
// P is each key of T in turn, like a for..in loop over type keys

type Stringify<T> = {
  [P in keyof T]: string; // every property becomes a string
};

interface User { id: number; name: string; active: boolean; }
type StringifiedUser = Stringify<User>;
// { id: string; name: string; active: string; }

// Preserve the original value type using T[P] (indexed access)
type Identity<T> = {
  [P in keyof T]: T[P]; // copies the type exactly
};
// Identity<User> === User (structurally)

Adding and Removing Modifiers

Mapped types can add or remove the readonly and ? (optional) modifiers using + (add) and - (remove) prefixes:

// Adding modifiers — + is implicit (same as no prefix)
type MyReadonly<T> = {
  +readonly [P in keyof T]: T[P]; // add readonly to every property
};
type MyPartial<T> = {
  [P in keyof T]+?: T[P]; // add ? to every property
};

// Removing modifiers — the - prefix
type MyRequired<T> = {
  [P in keyof T]-?: T[P]; // -? removes the optional modifier
};
type MyMutable<T> = {
  -readonly [P in keyof T]: T[P]; // -readonly removes readonly
};

// Combining: make every property required AND mutable
type Concrete<T> = {
  -readonly [P in keyof T]-?: T[P];
};

Key Remapping with as

TypeScript 4.1+ allows renaming keys in mapped types using an as clause — enabling prefix addition, case transformation, and key filtering:

// Add a 'get' prefix to every key
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

interface User { name: string; email: string; id: number; }
type UserGetters = Getters<User>;
// { getName: () => string; getEmail: () => string; getId: () => number; }

// Filter keys using never — never keys are omitted from the output
type OnlyStrings<T> = {
  [P in keyof T as T[P] extends string ? P : never]: T[P];
};
type UserStringFields = OnlyStrings<User>;
// { name: string; email: string; } — id (number) is filtered out

// Map to event handler keys
type EventHandlers<T> = {
  [P in keyof T as `on${Capitalize<string & P>}`]: (value: T[P]) => void;
};

Implementing Built-in Utility Types

Every built-in utility type is a mapped type. Understanding their implementations is the mark of a senior TypeScript developer:

// Partial — adds ?
type Partial<T> = { [P in keyof T]?: T[P]; };

// Required — removes ?
type Required<T> = { [P in keyof T]-?: T[P]; };

// Readonly — adds readonly
type Readonly<T> = { readonly [P in keyof T]: T[P]; };

// Pick — only iterates over the specified keys K
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };

// Record — K is any union of keys, V is the value type
type Record<K extends keyof any, V> = { [P in K]: V; };

// These are all in TypeScript's own lib.es5.d.ts — reading the source is instructive

Mapped Types with Conditional Types

Combining mapped types with conditional types enables powerful type transformations:

// Deep Partial — recursively makes all nested properties optional
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// Make only specific properties optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface User { id: number; name: string; email: string; }
type UserCreate = PartialBy<User, "id">;
// { id?: number; name: string; email: string; } — id optional, rest required

// Extract function-valued properties
type FunctionProperties<T> = {
  [P in keyof T as T[P] extends Function ? P : never]: T[P];
};

// Flatten a nested type by one level
type Flatten<T> = {
  [P in keyof T]: T[P] extends Array<infer U> ? U : T[P];
};

Distributive Mapped Types over Unions

// When T is a union, the mapped type applies to each member separately
type NullableFields<T> = { [P in keyof T]: T[P] | null };

// Works correctly over unions via distributive keyof behaviour
type Config = { host: string; port: number };
type NullableConfig = NullableFields<Config>;
// { host: string | null; port: number | null; }

Common Misconceptions

⚠️

Many developers think mapped types are only for making properties optional or readonly — those are just two applications. Mapped types can rename keys, filter keys, transform value types, add methods, create event handler maps, and generate entirely new type shapes from existing ones.

⚠️

Many developers think you cannot filter out properties in a mapped type — mapping a key to never in an as clause removes it from the output type. This enables conditional key filtering in a single mapped type expression.

⚠️

Many developers think Partial<T> makes nested objects optional recursively — it only applies to the top level. DeepPartial requires a recursive mapped type with a conditional type to handle nested objects.

⚠️

Many developers think the -readonly and -? modifier syntax is specific to utility types and not available in user-defined mapped types — these modifiers work in any mapped type you write. They are part of the mapped type syntax, not special-cased for built-ins.

⚠️

Many developers think key remapping (as clause) is only for adding prefixes — the as clause can produce any template literal string, filter keys to never, remap to a computed union, or even reverse-lookup a key from a value type.

⚠️

Many developers treat mapped types as read-only reference material — they are a composable building block. Combining Pick, Omit, Partial, and custom mapped types with & intersections covers most real-world type transformation needs without conditional types.

Where You'll See This in Real Code

Form validation schemas: mapped types generate a validation schema type from a data model — { [P in keyof FormData]: Validator<FormData[P]> } — keeping the schema and model in sync automatically when fields are added or removed.

API client generation: tools like openapi-typescript use mapped types to transform OpenAPI schemas into TypeScript types with correct optionality, readonly, and nullable flags derived from the schema definition.

ORM column mappers: TypeORM and Prisma generate mapped types for entity relations, partial update inputs, and select objects — all built with variations of Pick, Partial, and custom mapped types over the entity type.

React component prop transformation: Pick<ButtonProps, RequiredKeys> & Partial<Pick<ButtonProps, OptionalKeys>> is a common pattern for wrapper components that re-expose a strict subset of props with adjusted optionality.

Setter/getter generation: framework code that wraps store objects often uses Getters<State> and Setters<State> mapped types to generate typed accessor interfaces from a plain state shape.

Deep readonly for Redux state: Readonly<DeepReadonly<State>> using a recursive mapped type ensures immutability throughout the entire state tree, making accidental mutations compile errors.

Interview Cheat Sheet

  • Basic: { [P in keyof T]: T[P] } — iterate over T's keys, preserve each value type
  • Transform value: { [P in keyof T]: SomeType<T[P]> } — apply a transformation to each value
  • +? adds optional, -? removes optional; +readonly adds readonly, -readonly removes it
  • Key remap: { [P in keyof T as NewKeyExpression]: T[P] } — rename or filter keys
  • Filter keys: map key to never in as clause to exclude it from the output type
  • Template literal keys: as `get${Capitalize<string & P>}` generates getX method names
  • Pick = mapped type iterating over a subset K of keys: { [P in K]: T[P] }
  • Record = mapped type iterating over a union K: { [P in K]: V }
  • Partial = mapped type with +?: { [P in keyof T]?: T[P] }
  • Combine with conditionals for deep transformations: T[P] extends object ? Recurse<T[P]> : T[P]
💡

How to Answer in an Interview

  • 1.Open by implementing Partial from scratch: '[P in keyof T]?: T[P] — this is the entire implementation of Partial. Every built-in utility type is a mapped type, and understanding these implementations is more useful than memorizing utility type names.'
  • 2.The modifier syntax is often unknown to mid-level candidates — mention it: 'Mapped types can add or remove the ? and readonly modifiers using +/- prefixes. Required<T> is implemented as -?: T[P] — the minus removes the optional modifier. This is in lib.es5.d.ts if you want to read it.'
  • 3.Key remapping is a strong senior-level signal: 'TypeScript 4.1 added an as clause to mapped types for key renaming. This lets you generate getX accessor names, filter out keys that map to never, and transform property names using template literal types.'
  • 4.Connect mapped types to real problems: 'The most practical use I encounter is PartialBy<T, K> — a type that makes only specific properties optional while requiring the rest. It's two lines using mapped types but saves writing a custom interface for every update shape.'

Practice Questions

1 question
#01

What is the Mapped Type pattern in utility types — how are Partial, Readonly, and Record implemented?

MediumUtility Types PRO💡 for...in at the type level — iterate over keyof T and transform each property

Related Topics

TypeScript Types vs Interfaces — Complete Interview Guide
Intermediate·8–12 Qs
TypeScript Generics — Complete Interview Guide
Intermediate·8–12 Qs
TypeScript Utility Types — Complete Interview Guide
Intermediate·6–10 Qs
TypeScript Conditional Types — Complete Interview Guide
Advanced·6–10 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz