Skip to content
#237 unique-tuple · par 4

    ↑↓ move · ⏎ open · esc close

    No. 237 · August 20, 2026 · Hard

    Unique

    Implement `Unique<T>` so it removes duplicate elements from a tuple, keeping the first occurrence of each. `Includes` from puzzle #227 is in scope.

    01

    Try the puzzle yourself

    Par 4

    Puzzle

    unique-tuple.ts
    type Includes<T extends readonly unknown[], U> =
      T extends readonly [infer H, ...infer R]
        ? Equal<H, U> extends true
          ? true
          : Includes<R, U>
        : false
    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

    4
    • Unique<[1, 2, 2, 3, 1]>
      [1, 2, 3]
    • Unique<[]>
      []
    • Unique<['a', 'b']>
      ['a', 'b']
    • Unique<[1, 1, 1]>
      [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 August 21, 2026

    The solution

    type Unique<
      T extends readonly unknown[],
      Acc extends unknown[] = [],
    > = T extends readonly [infer H, ...infer R]
      ? Includes<Acc, H> extends true
        ? Unique<R, Acc>
        : Unique<R, [...Acc, H]>
      : Acc

    The common wrong answer

    type Unique<T extends readonly unknown[]> =
      T extends readonly [infer H, ...infer R]
        ? Includes<R, H> extends true
          ? Unique<R>
          : [H, ...Unique<R>]
        : []

    This looks ahead instead of remembering. When an element recurs later it drops the *first* occurrence and keeps the last, so `[1, 2, 2, 3, 1]` becomes `[2, 3, 1]`. The elements are right, the order is not.

    Line by line

    1. Acc extends unknown[] = []

      A defaulted type parameter is how a type-level function carries state. The caller never passes it; each recursive call threads the growing result through.

    2. Includes<Acc, H>

      The membership test runs against what has already been kept, not against what is still to come. That is the difference between keeping the first occurrence and keeping the last.

    3. : Acc

      When the input is exhausted the accumulator *is* the answer, so it is returned directly rather than built up on the way out. This is a tail-recursive shape, and TypeScript handles it in less stack than the head-and-tail form.

    Takeaway

    An accumulator parameter turns a backwards-building recursion into a forwards-building one. It costs a type parameter and buys both correct ordering and a deeper recursion limit.

    Uses