Resourcesโ€บTypeScript Tipsโ€บMapped Types and Template Literals: TypeScript's Hidden Power
๐Ÿ“˜TypeScript Tipsโ€” Mapped Types and Template Literals: TypeScript's Hidden Powerโฑ 7 min

Mapped Types and Template Literals: TypeScript's Hidden Power

Mapped types and template literal types let you transform and generate types programmatically. Here's how to use them in real code.

๐Ÿ“…February 5, 2026โœTechTwitter.iotypescriptmapped-typestemplate-literals

What Are Mapped Types?

A mapped type creates a new type by iterating over the keys of an existing type:

type Optional<T> = {
  [K in keyof T]?: T[K]
}
// Equivalent to Partial<T> โ€” same idea as built-in utility types

interface User {
  id: string
  name: string
  email: string
}

type OptionalUser = Optional<User>
// { id?: string; name?: string; email?: string }

The [K in keyof T] is the mapped type syntax. It says: "for each key K in T, create a property."


Modifiers: ? and readonly

You can add or remove ? (optional) and readonly modifiers:

// Add readonly
type Immutable<T> = {
  readonly [K in keyof T]: T[K]
}

// Remove readonly (note the -)
type Mutable<T> = {
  -readonly [K in keyof T]: T[K]
}

// Remove optional
type Required<T> = {
  [K in keyof T]-?: T[K]
}

The - prefix removes a modifier. This is how TypeScript's built-in Required<T> is implemented.


Transforming Value Types

Change what each property contains:

// Wrap every value in a Promise
type Async<T> = {
  [K in keyof T]: Promise<T[K]>
}

// Wrap every value in a getter function
type Lazy<T> = {
  [K in keyof T]: () => T[K]
}

// Make every value nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null
}

Key Remapping with as

Remap key names in the mapped type:

// Add "get" prefix to all methods
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface Store {
  count: number
  name: string
}

type StoreGetters = Getters<Store>
// { getCount: () => number; getName: () => string }

Template Literal Types

Template literal types work like JavaScript template strings but at the type level:

type EventName = 'click' | 'focus' | 'blur'

// Generate 'onClick' | 'onFocus' | 'onBlur'
type EventHandler = `on${Capitalize<EventName>}`

Real Example: CSS Properties

type Side = 'top' | 'right' | 'bottom' | 'left'
type Axis = 'x' | 'y'

// Generate 'margin-top' | 'margin-right' | 'margin-bottom' | 'margin-left'
type MarginProp = `margin-${Side}`

// Generate 'padding-x' | 'padding-y'
type PaddingAxis = `padding-${Axis}`

Real Example: Typed Event Emitter

type EventMap = {
  userCreated: { userId: string }
  orderPlaced: { orderId: string; total: number }
  emailSent: { to: string }
}

type EventHandler<T extends keyof EventMap> = (payload: EventMap[T]) => void

// Generate `onUserCreated`, `onOrderPlaced`, etc.
type EventHandlers = {
  [K in keyof EventMap as `on${Capitalize<string & K>}`]: EventHandler<K>
}

// Type-safe event bus
interface EventBus extends EventHandlers {
  emit<K extends keyof EventMap>(event: K, payload: EventMap[K]): void
}

Filtering Keys with Conditional Types

Remap keys to never to filter them out:

// Only keep string-valued properties
type StringKeys<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 StringOnly = StringKeys<Mixed>
// { name: string; email: string }

Combining Mapped + Template Types: Deep Transform

// Flatten a nested object type into dot-notation keys
type DotPaths<T, Prefix extends string = ''> = {
  [K in keyof T & string]: T[K] extends object
    ? DotPaths<T[K], `${Prefix}${K}.`>
    : `${Prefix}${K}`
}[keyof T & string]

interface Config {
  db: { host: string; port: number }
  app: { name: string; debug: boolean }
}

type ConfigPaths = DotPaths<Config>
// 'db.host' | 'db.port' | 'app.name' | 'app.debug'

// Now you can type-check config lookups
function getConfig<P extends ConfigPaths>(path: P): string { ... }
getConfig('db.host')    // โœ…
getConfig('db.wrong')   // โŒ Error

Key Takeaways

  • Mapped types let you transform every property of a type programmatically
  • Use -? and -readonly to remove modifiers
  • Key remapping with as lets you rename keys, including with template literals
  • Template literal types work like JS template strings at the type level
  • Filter keys by mapping to never in the key remapping clause
  • These features compose โ€” mapped + conditional + template = very powerful transformations