Kodokon kodokon.com

Mapped types and key remapping: transforming types

Understand how homomorphic mapped types preserve modifiers and how the as clause renames or filters keys.

8 min · 3 questions

Open this lesson in Kodokon

A mapped type iterates over a set of keys to produce a new object type. The [K in keyof T] form is called homomorphic: the compiler knows the structure comes from T and automatically copies the readonly and ? modifiers of each original property. The + and - prefixes let you explicitly add or remove these modifiers: this is exactly the internal machinery behind Partial, Required, and Readonly.

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

interface Config {
  readonly host?: string;
  readonly port?: number;
}

type Draft = Mutable<Config>;
// { host: string; port: number }
The - prefixes remove readonly and optionality.

Since TypeScript 4.1, the as clause enables key remapping: each key can be renamed on the fly, typically through a template literal type. And if the clause produces never, the key is simply removed from the result: this is the official property-filtering mechanism. Note the & string in the example: keyof T may contain symbol or number keys, which are incompatible with a template literal.

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

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

type OnlyMethods<T> = {
  [K in keyof T as
    T[K] extends (...args: never[]) => unknown
      ? K : never]: T[K];
};
Renaming with as, filtering by producing never.

A specification subtlety that is often overlooked: applied to an array or a tuple, a homomorphic mapped type does not produce an object with the keys 0, 1, and length. The compiler preserves the nature of the type: it transforms the elements and keeps the array or tuple shape. This is what allows Readonly<[number, string]> to remain a tuple. That preservation disappears as soon as the mapped type is no longer homomorphic, for example with [P in K] where K is an arbitrary union of keys.

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

type Pair = Immutable<[number, string]>;
// readonly [number, string]: the tuple survives

type List = Immutable<string[]>;
// readonly string[]
A homomorphic mapped type respects arrays and tuples.

Knowledge check

Make sure you remember the key points of this lesson.

  1. In a key remapping, what happens when the as clause produces never for a key?
    • The property gets the type never
    • The key is removed from the resulting type
    • The compiler emits an error
  2. What does { readonly [K in keyof T]: T[K] } produce when applied to the tuple [number, string]?
    • An object with the keys 0, 1, and length
    • readonly [number, string]
    • readonly (number | string)[]
    • A compilation error
  3. What is the effect of the -? modifier in a mapped type?
    • It makes the property optional
    • It removes optionality and the undefined it introduced
    • It removes the readonly modifier
    • It removes the property from the type