`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.
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.