Mapped Types

Hard

Mapped types let you create new types by transforming each property of an existing type. They use the `in keyof` syntax to iterate over property keys and apply transformations such as making properties optional, readonly, or changing their value types. All of TypeScript's built-in utility types like Partial, Required, and Readonly are implemented as mapped types.

Interactive Visualization

Key Points

  • Syntax: `{ [K in keyof T]: TransformedType }` iterates over all keys of T
  • Modifiers `?` (optional) and `readonly` can be added or removed with `+` / `-`
  • Key remapping with `as` lets you rename or filter keys: `{ [K in keyof T as NewKey]: T[K] }`
  • Template literal types in key remapping enable pattern-based key transformations
  • Mapped types can be combined with conditional types for powerful type-level logic
  • The `-?` modifier removes optionality (used by `Required<T>` internally)

Code Examples

Basic Mapped Types

// Make all properties optional (this is how Partial works)
type MyPartial<T> = {
  [K in keyof T]?: T[K]
}

// Make all properties required (remove ?)
type MyRequired<T> = {
  [K in keyof T]-?: T[K]
}

// Make all properties readonly
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K]
}

// Make all property values nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null
}

interface Config {
  host: string
  port: number
  debug?: boolean
}

type NullableConfig = Nullable<Config>
// { host: string | null; port: number | null; debug?: boolean | null }

Mapped types iterate over each key in a type and produce a new type. The `in keyof` clause enumerates all property keys, and you can transform both keys and values.

Key Remapping with `as`

// Prefix all keys with "get"
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface Person {
  name: string
  age: number
}

type PersonGetters = Getters<Person>
// { getName: () => string; getAge: () => number }

// Filter keys by value type — keep only string properties
type StringKeysOnly<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K]
}

interface Mixed {
  name: string
  age: number
  email: string
  active: boolean
}

type StringProps = StringKeysOnly<Mixed>
// { name: string; email: string }

Key remapping with `as` lets you transform or filter property keys. Mapping a key to `never` removes it from the output type, which is how you filter properties by their value type.

Event Handler Map Pattern

// Real-world pattern: generate event handler types from an event map
interface EventMap {
  click: { x: number; y: number }
  focus: { target: HTMLElement }
  submit: { data: FormData }
}

type EventHandlers<T> = {
  [K in keyof T as `on${Capitalize<string & K>}`]: (event: T[K]) => void
}

type Handlers = EventHandlers<EventMap>
// {
//   onClick: (event: { x: number; y: number }) => void
//   onFocus: (event: { target: HTMLElement }) => void
//   onSubmit: (event: { data: FormData }) => void
// }

// Make specific keys optional, rest required
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

type UserWithOptionalEmail = PartialBy<Person & { email: string }, 'email'>

Mapped types with template literal keys enable powerful patterns like auto-generating event handler interfaces from event definitions. This is a common pattern in UI frameworks.

Common Mistakes

  • Forgetting the `string &` intersection when using template literal key remapping (K may be symbol)
  • Not realizing that mapped types distribute over union types in `keyof`
  • Confusing the `-?` modifier (remove optional) with just `?` (add optional)
  • Writing overly complex mapped types that are difficult to debug
  • Not knowing that homomorphic mapped types preserve modifiers from the original type

Interview Tips

  • Be able to implement Partial, Required, and Readonly from scratch — they are all mapped types
  • Key remapping with `as` is an advanced feature that impresses interviewers
  • Explain how `never` in key position removes a property from the mapped type
  • Mention that built-in utility types are mapped types under the hood

Related Concepts