Engrain by ByteRush

TS.05.03 Mid-level· 10 minCore — asked often

Mapped types and key remapping: transforming every property at once

Build new object types from old ones with { [K in keyof T]: ... }, add or remove readonly and ?, rename keys with as, and drop keys by mapping them to never.

Comes up in: Coding

Learn#

Leena has a User type and needs three more: an editable draft, a version where everything is optional for PATCH requests, and getter methods for a class. Writing them by hand means four types to keep in sync.

A mapped type loops over keys and builds a new property for each: { [K in keyof T]: T[K] } copies T. Change the right side, and you change every property at once.

Key remapping with as renames keys, and mapping a key to never drops it:

type User = { readonly id: number; name: string; email?: string };

type Draft<T> = { -readonly [K in keyof T]-?: T[K] };
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
type OnlyStrings<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] };

const draft: Draft<User> = { id: 1, name: "Asha" };
const getters: Getters<User> = { getId: () => 1, getName: () => "Asha", getEmail: () => 5 };
const strings: OnlyStrings<User> = { name: "Asha", id: 1 };
TypeScript compiler output (verified)
example.ts(7,7): error TS2741: Property 'email' is missing in type '{ id: number; name: string; }' but required in type 'Draft<User>'.
example.ts(8,73): error TS2322: Type '() => number' is not assignable to type '() => string | undefined'.
  Type 'number' is not assignable to type 'string'.
example.ts(9,52): error TS2353: Object literal may only specify known properties, and 'id' does not exist in type 'OnlyStrings<User>'.

-? made email required, the getters kept each value’s type, and id was filtered out.

Mapping keys, renaming them and dropping someStep 1 of 4

State

  • K in "id" | "name" | "email"

keyof User gives the keys to loop over: "id" | "name" | "email".

Read the steps as text
  1. keyof User gives the keys to loop over: "id" | "name" | "email".
  2. K = "id": as renames it to getId, and the value becomes () => number.
  3. K = "name" and K = "email" go the same way.
  4. A key renamed to never is dropped. That is how OnlyStrings removes id.

Predict#

Predict first

type Copy<T> = { [K in keyof T]: T[K] }. Is Copy<{ readonly id: number }>['id'] still readonly?

Interview angle#

Say it like this — about 30 seconds

“A mapped type loops over a set of keys, usually keyof T, and builds a property for each, like a map over an object type. Readonly maps every key K of T to a readonly copy of T[K], and Partial adds a question mark. Prefixing with minus removes a modifier. Since TypeScript 4.1, as renames keys, often with template literal types, and renaming a key to never removes it. Mapping over keyof T keeps the original modifiers.”

Stays on your device. Nothing is uploaded.
Follow-up Implement Readonly<T>.
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
Follow-up How do you remove optional from every property?
Use -?: { [K in keyof T]-?: T[K] }. That is how Required works.
Follow-up How do you keep only function properties?
Remap with a check: as T[K] extends (...a: any[]) => any ? K : never.

Practice#

Guided

Write five mapped types without using the built-in utility types.

Done when

  • MyReadonly, MyPartial and Mutable change only the modifiers.
  • Getters<{ name: string }> is exactly { getName: () => string }.
  • PickByValue<T, string> keeps only string properties.
Your solution
Ctrl/⌘ + Enter

Remember#

Try to answer each card before you open it. Then add them to your reviews.

Write the mapped type that copies T.

{ [K in keyof T]: T[K] }

How do you remove readonly in a mapped type?

-readonly before the key: { -readonly [K in keyof T]: T[K] }

How do you drop a key in a mapped type?

Remap it to never with as.

What is a homomorphic mapped type?

One that maps over keyof T and keeps T's modifiers.

Go deeper

Four layers. Stop at the one your interview needs; each ends with a quick check.

The core ideaEvery round
Add readonly or ? to put a modifier on every property; prefix - to remove it, or + to be explicit. Partial, Required and Readonly are exactly these three mapped types.
Check yourself: Write Partial<T>.

{ [K in keyof T]?: T[K] }

How it works underneathSenior rounds

as takes any type that becomes the new key. Keys can be strings, numbers or symbols, so template literals need string & K Source: TypeScript Handbook: Mapped types.

Check yourself: Why does as need string & K in a template literal?

Keys may be numbers or symbols; string & K keeps only string keys, which a template can use.

Edge cases and trapsWhere people slip
Mapping a key to never removes it.
A homomorphic mapped type, written over keyof T, keeps T’s modifiers, and on arrays and tuples it maps the elements and returns an array or tuple.
Check yourself: What does a homomorphic mapped type do when T is an array?

It maps the elements and returns an array or tuple type.

In productionStaff-level answers

Partial, Required, Readonly, Pick and Record are mapped types Source: TypeScript Handbook: Utility types. Use them for form state, API patch payloads and event-handler maps, and derive from one source type.

Check yourself: Which built-in types are mapped types?

Partial, Required, Readonly, Pick and Record.

Finished the five steps? Mark it, and its cards join your reviews.

Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

Interview questions this answers

  • What is a mapped type? Implement Readonly<T>.