#6141β€’Hard

Binary to Decimal

Convert a binary string literal like '1010' into the number type 10. The type system has no arithmetic, so tuples stand in for numbers and spreads do the math.

Convert '1010' into the number 10 without a single arithmetic operator.

There is no + and no * in the type system, which turns "read this string as a binary number" into a real puzzle. BinaryToDecimal<S> takes an exact string type S consisting of 0s and 1s and returns the exact number type you get when S is read as binary. You can assume S is at most 8 characters long and never empty.

type Res1 = BinaryToDecimal<'10'>; // expected to be 2
type Res2 = BinaryToDecimal<'0011'>; // expected to be 3

The question underneath: how do you compute 2 * n + bit when you can't multiply or add? The answer, counting with tuple lengths, is one of the most reusable tricks in advanced TypeScript.

Challenge Instructions: Binary to Decimal

Hard

Implement BinaryToDecimal<S> which takes an exact string type S consisting 0 and 1 and returns an exact number type corresponding with S when S is regarded as a binary. You can assume that the length of S is equal to or less than 8 and S is not empty.

type Res1 = BinaryToDecimal<'10'>; // expected to be 2
type Res2 = BinaryToDecimal<'0011'>; // expected to be 3

View on GitHub: https://tsch.js.org/6141

Change the following code to make the test cases pass (no type check errors).

πŸ‘‹ Lifetime-License is leaving on August 10, 2026

Get it now for $29

One-time payment. Lifetime access to all pro challenges.

Loading...

Detailed Explanation

The whole solution is one recursive type:

type BinaryToDecimal<
  S extends string,
  Acc extends unknown[] = [],
> = S extends `${infer Bit}${infer Rest}`
  ? Bit extends '1'
    ? BinaryToDecimal<Rest, [...Acc, ...Acc, unknown]>
    : BinaryToDecimal<Rest, [...Acc, ...Acc]>
  : Acc['length']

Short, but it packs three separate ideas.

Numbers as tuple lengths

The type system can't add or multiply, but it can tell you the length of a tuple: ['a', 'b', 'c']['length'] is the literal type 3, not just number. So the standard workaround is to represent a number n as a tuple with n elements, do the arithmetic by building bigger tuples with spreads, and read off ['length'] at the very end.

That's what the accumulator Acc extends unknown[] = [] is: the running decimal value, encoded as a tuple. It starts empty, i.e. at zero. The element type doesn't matter, only the count does, which is why unknown is used.

Two operations fall out for free:

Walking the string one character at a time

S extends `${infer Bit}${infer Rest}` splits the string using template literal inference. When the first infer in such a pattern is immediately followed by another, it matches exactly one character: for S = '0011', Bit is '0' and Rest is '011'. Each recursive call therefore consumes one bit, left to right, until the string is empty. At that point the pattern no longer matches and we hit the base case.

The double-and-add algorithm

Why double on every bit? Because that's how positional notation works. Reading '1010' left to right, you can maintain a running value: start at 0, and for each new bit compute value = value * 2 + bit. Step by step:

// S = '1010', Acc = []            β†’ value 0
// bit '1': Acc = [_]              β†’ value 0 * 2 + 1 = 1
// bit '0': Acc = [_, _]           β†’ value 1 * 2 + 0 = 2
// bit '1': Acc = [_, _, _, _, _]  β†’ value 2 * 2 + 1 = 5
// bit '0': ten elements           β†’ value 5 * 2 + 0 = 10

The two conditional branches implement exactly this: a '1' doubles and appends one ([...Acc, ...Acc, unknown]), a '0' just doubles ([...Acc, ...Acc]). When the string runs out, Acc['length'] collapses the tuple back into a literal number type: for '1010', that's 10.

Note the recursion is tail-recursive with an accumulator: instead of computing something on the way back up, each call passes the updated state forward. This pattern keeps type-level recursion shallow (one level per character) and is the same shape you'd use for type-level Reverse, StringToNumber, and friends.

Edge cases the tests cover

The takeaway: for type-level arithmetic, tuples act as unary numbers and spreads act as operators, with ['length'] as the way back to an actual number.

This challenge is originally from here.

Share this challenge

Learn the Concepts