Implement the % operator at the type level. Numbers become tuples, the divisor is peeled off the front repeatedly, and the leftover length is the remainder.
The type system has no % operator, and no arithmetic at all. You build it out of tuple lengths.
Modulo<M, N> returns the remainder of M divided by N, the same value JavaScript's % gives you for non-negative integers. Division itself never appears in the solution: you count instead. This counting-with-tuples pattern is the foundation of every type-level arithmetic challenge, so it pays off well beyond this one.
5 % 4 === 1 // Since 5 divided by 4 is 1 with a remainder of 1
9 % 4 === 1 // Since 9 divided by 4 is 2 with a remainder of 1
16 % 2 === 0 // Since 16 divided by 2 is 8 with a remainder of 0You do not have to handle negative operands.
Javascript and a lot of other programming languages support the modulo (also know as remainder) operator % which returns the remainder when an integer is divided by another integer. For Javascript's implementation - see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder.
Here's some examples:
5 % 4 === 1 // Since 5 divided by 4 is 1 with a remainder of 1
9 % 4 === 1 // Since 9 divided by 4 is 2 with a remainder of 1
16 % 2 === 0 // Since 16 divided by 2 is 8 with a remainder of 0You do not need to worry about the case when the left or right hand sides are negative.
View on GitHub: https://tsch.js.org/14078
Change the following code to make the test cases pass (no type check errors).
/* _____________ Your Code Here _____________ */
type Modulo<M extends number, N extends number> = any
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '../helpers'
type cases = [
Expect<Equal<Modulo<5, 4>, 1>>,
Expect<Equal<Modulo<9, 4>, 1>>,
Expect<Equal<Modulo<16, 2>, 0>>,
Expect<Equal<Modulo<35, 1>, 0>>,
Expect<Equal<Modulo<35, 10>, 5>>
]
Get access to all 200+ challenges, including every medium, hard, and extreme one.
Monthly subscription.
Cancel anytime + 30-day money-back guarantee.
The solution in full:
type Tuple<N extends number, Acc extends unknown[] = []> = Acc['length'] extends N
? Acc
: Tuple<N, [...Acc, unknown]>
type Remainder<M extends unknown[], N extends unknown[]> = M extends [...N, ...infer Rest]
? Remainder<Rest, N>
: M['length']
type Modulo<M extends number, N extends number> = Remainder<Tuple<M>, Tuple<N>>Three small types, each doing one job.
TypeScript cannot add or subtract numeric literals, but it can read ['length'] off a tuple and it can build tuples with the spread syntax. That gives you a bridge in both directions: 5 becomes a five-element tuple, and a five-element tuple becomes 5 again.
Tuple<N> crosses that bridge. The second parameter Acc is an accumulator: a working value that gets threaded through the recursion with a default, so the caller never has to pass it. Each step compares the length built so far against the target and either stops or appends one more unknown.
// Tuple<3> unfolds like this:
// Acc = [] → 0 extends 3? no → recurse
// Acc = [unknown] → 1 extends 3? no → recurse
// Acc = [unknown, unknown] → 2 extends 3? no → recurse
// Acc = [unknown, unknown, unknown] → 3 extends 3? yes → return AccThe element type is irrelevant here. Only the length carries information, so unknown is as good a filler as any.
Remainder is where the actual work happens, and it rests on one pattern:
[object Object]Read that as a question about shape: does M start with as many elements as N has? A tuple pattern may contain one rest element whose length is open. Since N is a concrete tuple by the time Remainder runs, its length is fixed, so there is exactly one way to split M, and Rest captures everything after that prefix. The infer keyword declares Rest on the spot and binds it to whatever matched.
That single check is subtraction in disguise. Matching means M is at least N long, and Rest is M minus N elements.
// Remainder<Tuple<9>, Tuple<4>>
// M has 9 elements → strip 4 → Rest has 5 → recurse
// M has 5 elements → strip 4 → Rest has 1 → recurse
// M has 1 element → no match → return 1When the pattern stops matching, fewer than N elements are left, which is precisely the definition of a remainder. At that point M['length'] converts the tuple back into a numeric literal and the recursion unwinds.
Every recursive call of Remainder is made with a strictly shorter tuple, because the match only succeeds when at least one full divisor can be removed. A shrinking tuple cannot shrink forever, so the chain always reaches the non-matching branch. Tuple terminates for the same reason read backwards: Acc grows by one element per step and Acc['length'] eventually hits N.
Both recursions are bounded by the operands, not by a fixed budget, so the usual instantiation-depth limit applies. Values in the low hundreds are comfortable; five-digit operands are not.
Modulo<16, 2> → 0: the division comes out even, so the last successful match leaves an empty tuple and []['length'] is 0.Modulo<35, 1> → 0: a divisor of one strips a single element per step, so this is the slowest of the tests at 35 recursive calls, and it still lands on 0.Modulo<5, 4> → 1: the pattern matches once and then fails, which is the shortest possible run.Modulo<35, 10> → 5: three full chunks come off before the tail is too short to match.Negative operands are out of scope by the task statement, and a divisor of 0 has no meaningful answer here, the same way % 0 gives you NaN in JavaScript.
This challenge is originally from here.
Track your progress through 200+ hands-on challenges. Free, sign in with GitHub.
Or start solving right away: explore all TypeScript challenges