Annotated solution
Published August 21, 2026The 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
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.
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.
: AccWhen 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.
