2026-08-29 - Fibonacci, Computed by the TypeScript Compiler

tl;dr: TypeScript’s type system has no arithmetic, but tuples have a length. Build tuples to represent numbers, concatenate them to add, recurse with an accumulator pair — and Fib<10> resolves to the literal type 55 before a single line of code runs.


I keep a small pile of type-level puzzles around for interview prep, and this one is my favorite: compute Fibonacci numbers entirely at compile time. Not fib(10) returning 55 at runtime — the type Fib<10> being 55, something you can verify just by hovering in your editor.

The type system can’t add. There is no + for number literal types, so step one is representing a number as something the compiler can measure: a tuple. A tuple type knows its own length, and ["length"] reads it back as a literal.

// Build a tuple of length N so we can do arithmetic via ["length"].
type BuildTuple<N extends number, T extends unknown[] = []> = T["length"] extends N ? T : BuildTuple<N, [...T, unknown]>

It recursively appends unknown until the accumulator’s length matches N. So BuildTuple<3> is [unknown, unknown, unknown] — the number 3, encoded as “a thing of length 3”.

Once numbers are tuples, addition is free: concatenate the two tuples and read the combined length.

// Add by concatenating the two tuples and reading the combined length.
type Add<A extends number, B extends number> = [...BuildTuple<A>, ...BuildTuple<B>]["length"] & number

Now Fibonacci. The naive definition — Fib<N> = Add<Fib<N-1>, Fib<N-2>> — is a dead end here: there’s no subtraction, and the doubled recursion would blow past the compiler’s instantiation limits anyway. Instead, write the same loop you’d write at runtime: carry the pair (a, b), step it to (b, a + b), and count iterations with one more tuple.

// Iterate (a, b) → (b, a + b) until the index reaches Pos.
type Fib<
    Pos extends number,
    A extends number = 0,
    B extends number = 1,
    I extends unknown[] = [],
> = I["length"] extends Pos ? A : Fib<Pos, B, Add<A, B>, [...I, unknown]>
 
type Fib10 = Fib<10> // 55  (0,1,1,2,3,5,8,13,21,34,55)

I is the loop counter: it grows by one element per recursion, and when its length hits Pos, the answer is whatever A holds. Trace a couple of steps: (0, 1)(1, 1)(1, 2)(2, 3) → … exactly the iterative Fibonacci you already know, just spelled in type parameters instead of let.

The weird & number

The one non-obvious piece is the & number at the end of Add. It’s not arithmetic — it’s there to calm the constraint checker. While the recursion is still symbolic (A and B are unresolved type parameters), [...BuildTuple<A>, ...BuildTuple<B>]["length"] is a deferred indexed access — an unevaluated IOU, not a literal — and TypeScript refuses to assume a deferred type satisfies the extends number slots it gets fed back into. Intersecting with number gives the checker a concrete anchor: whatever the IOU resolves to, X & number is provably a number. And it costs nothing, because 55 & number collapses right back to 55. There’s no as for computed types; X & number is the type-level way of saying “trust me, this is a number”.

Where it breaks

Everything leans on tuple recursion, so large indices hit TypeScript’s instantiation depth limits — this is a party trick, not a math library. But the tools it’s built from are completely real: tuple-length arithmetic, accumulator-style recursion instead of naive branching, and the & number anchor all show up in serious type-level code, from route-param inference to typed pipe functions. Fibonacci is just the fun way to learn them.