Skip to content
webtype.org#226 rebuild-readonly · par 2

No. 226 · August 3, 2026 · Gentle

Rebuild Readonly

Implement `MyReadonly<T>` so every property becomes `readonly`. One level only — nested objects are left alone.

01

Try the puzzle yourself

Par 2

Puzzle

rebuild-readonly.ts
interface Config {
  host: string
  port: number
}
Stroke 1 of 2Not run yet

Replace ??? — your solution is checked against the cases below. Tab indents; press Escape then Tab to move focus out.

Checks

3
  • MyReadonly<Config>
    { readonly host: string; readonly port: number }
  • MyReadonly<{ a: string }>
    { readonly a: string }
  • MyReadonly<{}>
    {}

How a check is judged Exact type equality, not assignability — an intersection is not the same as the flattened object.

How everyone did

Fewer than 5 people have solved this one so far. The distribution appears once there is enough of a sample to mean anything.

Archive
02

Annotated solution

Published August 4, 2026

The solution

type MyReadonly<T> = { readonly [K in keyof T]: T[K] }

The common wrong answer

type MyReadonly<T> = { [K in keyof T]: Readonly<T[K]> }

This applies `Readonly` to each *value* instead of to the mapping. For a primitive like `string` that does nothing at all, so the keys stay mutable and the result is just a copy of `T`.

Line by line

  1. readonly [K in keyof T]

    The modifier precedes the key clause, mirroring how you would write `readonly host: string` in an interface. It marks the property, not the type of its value.

  2. T[K]

    The value passes through untouched, which is what keeps this shallow. Recursing here instead is exactly how `DeepReadonly` is built.

Takeaway

Modifiers act on properties; wrapping the value type is a different operation entirely. Confusing the two produces a type that looks right and enforces nothing.