Skip to content
webtype.org#251 deep-partial · par 4

    ↑↓ move · ⏎ open · esc close

    No. 251 · September 3, 2026 · Hard

    Optional all the way down

    Implement `DeepPartial<T>` so every property is optional at every level of nesting, not just the top one.

    01

    Try the puzzle yourself

    Par 4

    Puzzle

    deep-partial.ts
    Stroke 1 of 4Not run yet

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

    Checks

    3
    • DeepPartial<{ a: { b: string } }>
      { a?: { b?: string } }
    • DeepPartial<{ x: number }>
      { x?: number }
    • DeepPartial<{ a: { b: { c: 1 } } }>
      { a?: { b?: { c?: 1 } } }

    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.

    Short game

    Fewest characters

    No public scores yet.

    Archive
    02

    Annotated solution

    Published September 4, 2026

    The solution

    type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }

    The common wrong answer

    type DeepPartial<T> = { [K in keyof T]?: T[K] }

    That is `Partial`, and it stops at the first level. `{ a: { b: string } }` becomes `{ a?: { b: string } }` — the outer key is optional, and `b` is still as required as it ever was. Depth does not come for free; the type has to ask for it.

    Line by line

    1. T[K] extends object ? DeepPartial<T[K]> : T[K]

      The recursion lives in the value clause. Anything object-shaped goes back through `DeepPartial`; primitives are returned untouched, which is what terminates the descent.

    2. [K in keyof T]?:

      The `?` is applied at every level because every level is a fresh call to the same mapped type. One rule, written once, applied all the way down.

    Takeaway

    A recursive mapped type is just a mapped type that calls itself in the value position. The condition that stops it — here, "is this still an object?" — matters more than the recursion itself.

    Uses