Skip to content

Tower of Hanoi in JavaScript

The recursive solution in JavaScript, stepped through beside the board it moves — plus the two things about the language that change how you would really use it.

On this page
  1. The recursive solution, running
  2. Run it yourself
  3. Line by line
  4. Numbers that are not quite integers
  5. One move at a time, with a generator
  6. A disk count that is not a whole number
  7. The same algorithm in other languages
  8. Frequently asked questions

The recursive solution, running

Step through the program. The lit line is the one executing, the towers are labelled for the call that is running, and the stack lists every call still waiting to finish.

hanoi(3, "A", "C", "B")

Move 3 disks from A to C.

Nothing is running yet. The first step makes this call.

Step 0 of 43

Call stack

Depth 0 of 4 max

  1. Empty. Nothing has been called yet.

hanoi.js

function hanoi(n, source, target, spare) {
  if (n === 0) {
    return;
  }
  hanoi(n - 1, source, spare, target);
  console.log(`Move disk ${n} from ${source} to ${target}`);
  hanoi(n - 1, spare, target, source);
}

hanoi(3, "A", "C", "B");

Moves

7 in all

Choose a row to jump the program to that move.

Every move the program makes for 3 disks
MoveDiskFromTo
11AC
22AB
31CB
43AC
51BA
62BC
71AC

Run it yourself

Save the listing as hanoi.js and run it:

Run it

node hanoi.js

It prints the seven moves of the three-disk puzzle:

Output

Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C

Change the 3 in the call to solve a bigger puzzle. Every disk you add doubles the output, plus one line.

Line by line

Not what each line says — the listing says that — but why it has to be there:

LineWhy it is there
1Four parameters, because each call needs its own idea of which tower is the source, the target and the spare — the relabelling from one call to the next is what solves the puzzle. The names themselves are whatever the caller passes; below they are the strings "A", "B" and "C".
2–4Recursion needs a size it can answer without calling itself, and zero disks is that size. === is house style rather than a safeguard: after the first call n - 1 is always a Number, and on Numbers the two operators agree. Where they do differ, == is the one that stops — hanoi("0", …) ends at once, while under === it recurses past zero until the stack runs out.
5Disk n needs target empty when it arrives, so the n - 1 disks above it have to go somewhere else: spare. That is why the last two arguments swap — this call's spare is that call's target — and it is the whole trick of the algorithm in one line.
6The only line that moves anything. Line 5 has just taken every smaller disk off both the source and the target, which is what makes this move legal at this exact moment and at no other.
7Line 5 in reverse. The n - 1 disks are on spare, so it is the source now, and the tower they originally came from holds nothing smaller than n and can serve as the working space.
10Three disks from A to C, with B left over as the spare. A script needs no main: Node runs the file from top to bottom, and because a function declaration is hoisted to the top of its scope, this call would work even if it came before line 1.

Why those three lines are enough — the correctness proof, the recurrence and the complexity — is on the Tower of Hanoi algorithm page, and holds for every language here.

Numbers that are not quite integers

count-moves.js

// A Number is a 64-bit float: every integer is exact only up to 2 ** 53.
const asNumber = 2 ** 64 - 1;

// A BigInt is exact at any size. Every literal needs the n suffix.
function minimumMoves(n) {
  return 2n ** BigInt(n) - 1n;
}

console.log(`Number: ${asNumber}`);
console.log(`BigInt: ${minimumMoves(64)}`);

Run it

node count-moves.js

Output

Number: 18446744073709552000
BigInt: 18446744073709551615

Every JavaScript Number is a 64-bit floating-point value, and floating point represents integers exactly only up to 2⁵³ − 1, which is Number.MAX_SAFE_INTEGER. The 64-disk move count is far past that, so 2 ** 64 - 1 cannot be stored: the subtraction is lost to rounding, the result is 2⁶⁴ itself, and it prints as 18446744073709552000, which is simply the shortest decimal that names that float. There is no error and no warning — only a number that is wrong in its last digits.

BigInt holds integers of any size exactly. The price is an n on every literal and no mixing of the two kinds: 2n ** 64n - 1 throws a TypeError, because that 1 is a Number. The calculator on this site does all of its arithmetic in BigInt for exactly this reason.

One move at a time, with a generator

hanoi-generator.js

function* hanoi(n, source, target, spare) {
  if (n === 0) {
    return;
  }
  yield* hanoi(n - 1, source, spare, target);
  yield { disk: n, from: source, to: target };
  yield* hanoi(n - 1, spare, target, source);
}

// Nothing runs until a move is asked for, so the caller sets the pace.
const moves = hanoi(3, "A", "C", "B");
for (const { disk, from, to } of moves) {
  console.log(`Move disk ${disk} from ${from} to ${to}`);
}

Run it

node hanoi-generator.js

A function written function* is a generator. Calling it runs nothing; each call to next() runs it only as far as the next yield, and the value yielded comes back as the result. yield* hands control to another generator — here, the recursive call — until that one runs out.

The for...of loop at the bottom asks for every move straight away, but nothing forces it to. A page can instead call moves.next() from a timer or a button and draw one move per tick, while the recursion waits, call stack and all, exactly where it stopped. That makes the generator the natural shape for an animation: the solver and the drawing stay separate, and neither has to know about the other’s timing.

There is a cost. A move made deep in the recursion is passed up through every yield* above it, so each move takes work proportional to the depth of the call that made it — O(n) rather than O(1). At any size small enough to animate, that is far too little to notice.

A disk count that is not a whole number

JavaScript has no integer type, so nothing stops hanoi(2.5, "A", "C", "B") from being called — and a count like that never reaches the base case:

  • 2.5 counts down 2.5, 1.5, 0.5, −0.5 and on, stepping clean over zero.
  • A negative count does the same from a different start.
  • A string read from a form field works or fails by accident: "3" works, because n - 1 turns it into a Number on the first call, but "0" never equals 0 under ===.

All three recurse until the engine gives up with RangeError: Maximum call stack size exceeded.

So when a Tower of Hanoi function throws that RangeError, the base case is being missed — the recursion is almost never too deep. It is only n + 1 calls deep, and nobody waits for a puzzle big enough to fill the stack. The typed languages on this site stop the commonest version before the program runs: Java and C# refuse to compile a double passed as an int, while C and C++ quietly truncate 2.5 to 2 and solve a smaller puzzle, without a warning even under -Wall -Wextra. In JavaScript the check belongs at the door rather than in the recursion: test Number.isInteger(n) && n >= 0 once, before the first call.

The same algorithm in other languages

Frequently asked questions

How do you write Tower of Hanoi in JavaScript?

With a recursive function: if n is 0, return; otherwise call hanoi(n - 1, source, spare, target), log the move of disk n from source to target, and call hanoi(n - 1, spare, target, source). Calling hanoi(3, "A", "C", "B") logs the seven moves of the three-disk puzzle.

Why does JavaScript give the wrong move count for 64 disks?

JavaScript numbers are 64-bit floating-point values, which hold integers exactly only up to 2⁵³ − 1. 2 ** 64 − 1 is far past that, so the − 1 is lost to rounding and the result prints as 18446744073709552000. Use BigInt instead: 2n ** 64n - 1n is exactly 18446744073709551615.

How can I animate Tower of Hanoi moves in JavaScript?

Write the solver as a generator, with function* and yield, so it produces one move each time next() is called. Call next() from a timer and draw each move as it arrives; the recursion pauses between moves and picks up exactly where it left off.

Why does Tower of Hanoi in JavaScript throw "Maximum call stack size exceeded"?

Because the base case is never reached, not because the recursion is too deep: a correct solution is only n + 1 calls deep. A disk count that is not a whole number, such as 2.5, or a negative one steps over n === 0 and recurses until the engine's stack runs out. Check Number.isInteger(n) && n >= 0 once, before the first call.