typescript / expert
Snippet
Fixed-Length Tuple Types via Recursive Conditional Inference
`Array<T>` and `T[]` carry no length information at the type level, so a function expecting a 3-vector cannot reject a 2-vector at compile time. The tail-recursive `Tuple<T, N, R>` builder accumulates elements into `R` until `R['length']` literally matches `N`, at which point it returns `R` itself — a fixed-length tuple type. The default `R extends T[] = []` is the recursion seed and the reason callers only supply `Tuple<T, N>`. TypeScript caps recursive instantiations at 1000, so this works up to roughly that size; beyond it you need a divide-and-conquer doubling strategy.
snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Tuple<T, N extends number, R extends T[] = []> =R['length'] extends N ? R : Tuple<T, N, [...R, T]>;type Vec3 = Tuple<number, 3>; // [number, number, number]type RGB = Tuple<0 | 128 | 255, 3>; // strictly 3 channelsfunction dot<N extends number>(a: Tuple<number, N>,b: Tuple<number, N>): number {return a.reduce((s, x, i) => s + x * b[i], 0);}const v: Vec3 = [1, 2, 3];const w: Vec3 = [4, 5, 6];dot(v, w); // 32// dot(v, [1, 2] as const);// ^^^^^^^^^^^^^^^^^// Source has 2 element(s) but target requires 3.
Breakdown
1
type Tuple<T, N extends number, R extends T[] = []>
Three type parameters: element type, target length, and the accumulator with default `[]` so callers omit it.
2
R['length'] extends N ? R : Tuple<T, N, [...R, T]>
Base case: when the accumulator's literal length matches `N`, return it; otherwise recurse with one more element appended via tuple spread.
3
type Vec3 = Tuple<number, 3>;
Resolves to `[number, number, number]` — assignment of a wrong-length literal is now a compile error.
4
function dot<N extends number>(a: Tuple<number, N>, b: Tuple<number, N>)
`N` is inferred from `a`, so `b` is constrained to the same length — mismatched arities fail at the call site.