Skip to content
webtype.orgwebtype.org#266 zip-object · par 5

    ↑↓ move · ⏎ open · esc close

    No. 266 · September 18, 2026 · Brutal

    Zip an object

    Implement `ZipObject<Keys, Values>` so matching tuple positions become object properties. Stop when either tuple runs out.

    01

    Try the puzzle yourself

    Par 5

    Puzzle

    zip-object.ts
    type Prettify<T> = { [K in keyof T]: T[K] }
    Stroke 1 of 5Not run yet

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

    Checks

    3
    • ZipObject<['id', 'name'], [1, 'Ada']>
      { id: 1; name: 'Ada' }
    • ZipObject<['a'], [true, false]>
      { a: true }
    • ZipObject<['a', 'b'], [1]>
      { a: 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 19, 2026

    The solution

    type ZipObject<
      Keys extends readonly PropertyKey[],
      Values extends readonly unknown[]
    > = Prettify<
      Keys extends readonly [infer K extends PropertyKey, ...infer KR extends PropertyKey[]]
        ? Values extends readonly [infer V, ...infer VR]
          ? { [P in K]: V } & ZipObject<KR, VR>
          : {}
        : {}
    >

    The common wrong answer

    type ZipObject<Keys extends readonly PropertyKey[], Values extends readonly unknown[]> =
      Keys extends readonly [infer K extends PropertyKey, ...infer KR extends PropertyKey[]]
        ? Values extends readonly [infer V, ...infer VR]
          ? { [P in K]: V } & ZipObject<KR, VR>
          : {}
        : {}

    The properties are correct, but the result remains an intersection of one-property objects. Assignability accepts it; this checker asks for exact identity, so the final mapped pass is part of the answer.

    Line by line

    1. { [P in K]: V } & ZipObject<KR, VR>

      Each pass consumes one position from both tuples and intersects that property with the recursively built remainder.

    2. Prettify<

      Mapping over the accumulated keys materialises one ordinary object type, which is the shape the checks name.

    Takeaway

    Recursive object builders often accumulate intersections. A final identity mapping is not cosmetic when exact equality matters.

    Uses