Skip to content
webtype.org#213 unwrap-promise · par 3

No. 213 · July 21, 2026 · Moderate

Unwrap

Implement `Unwrap<T>` so it strips every layer of `Promise`, however deeply nested. A non-promise passes through unchanged.

01

Try the puzzle yourself

Par 3

Puzzle

unwrap-promise.ts
Stroke 1 of 3Not run yet

Replace ??? — your solution is checked against the cases below.

Checks

4
  • Unwrap<Promise<string>>
    string
  • Unwrap<Promise<Promise<number>>>
    number
  • Unwrap<boolean>
    boolean
  • Unwrap<Promise<Promise<Promise<null>>>>
    null

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

How everyone did

Nobody is counting yet. Score distribution and the short game board arrive with accounts — until then your results stay on this device.

Archive
02

Annotated solution

Published July 22, 2026

The solution

type Unwrap<T> = T extends Promise<infer Inner> ? Unwrap<Inner> : T

The common wrong answer

type Unwrap<T> = T extends Promise<infer Inner> ? Inner : T

This peels exactly one layer. `Promise<Promise<number>>` becomes `Promise<number>` and stops. The recursive call is what makes it total.

Line by line

  1. T extends Promise<infer Inner>

    Pattern-matches `T` against the shape `Promise<something>` and binds that something to `Inner`.

  2. ? Unwrap<Inner>

    Recursion. `Inner` may itself be a promise, so the same question is asked again until the answer is no.

  3. : T

    The base case. Not a promise, so `T` is already the value type.

Takeaway

A conditional type that calls itself in its true branch is the type-level equivalent of a `while` loop. The false branch is the exit.