Skip to content
webtype.orgTwelve concepts · 11/12

Concept 11 of 12

Variadic tuples

Tuples are the type level’s only data structure with a length, which makes them its only counter. Every piece of type-level arithmetic you will ever see is counting elements in a tuple wearing a disguise.

Spreads concatenate

type A = [1, 2]
type B = [3, 4]
type Joined = [...A, ...B]        // [1, 2, 3, 4]
type Prefixed = [0, ...A]         // [0, 1, 2]

type Len = Joined['length']       // 4  — a numeric literal type

For a fixed-length tuple, `length` is a numeric literal rather than `number`. That single fact is what makes arithmetic possible: build a tuple of the right size, then read its length back out.

Addition is concatenation, subtraction is matching

type Build<N extends number, Acc extends unknown[] = []> =
  Acc['length'] extends N ? Acc : Build<N, [...Acc, unknown]>

type Add<A extends number, B extends number> =
  [...Build<A>, ...Build<B>]['length']

type Sub<A extends number, B extends number> =
  Build<A> extends [...Build<B>, ...infer Rest] ? Rest['length'] : never

Building a tuple adds; taking one apart subtracts. The accumulator holds `unknown` because its contents are irrelevant — it is a tally of marks, not a collection of anything.

A rest element need not be last

type Last<T> = T extends readonly [...unknown[], infer L] ? L : never
type Init<T> = T extends readonly [...infer I, unknown] ? I : never

A tuple pattern may contain exactly one rest element, and it is allowed at the front. That is how you reach the end of a tuple without counting your way there.

The common wrong answer

// Intent: subtract.
type Sub<A extends number, B extends number> =
  [...Build<A>, ...Build<B>]['length']
// That is addition with a different name — concatenating
// can only ever make the tuple longer.

Subtraction has to take a tuple apart, which means pattern matching rather than building. If your arithmetic type only ever spreads, it can only ever add.

Takeaway

Tuple length is the only number the type system can count with. Build to add, destructure to subtract, and expect the recursion limit around a thousand.

Practiced in