Skip to content

Tower of Hanoi in C++

Idiomatic C++17: the recursion records its moves instead of printing them, and main decides what to do with the list. Step through it below.

On this page
  1. The recursive solution, running
  2. Run it yourself
  3. Line by line
  4. Returning moves, not printing them
  5. Counting the moves at compile time
  6. Without recursion, three pairs of towers
  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', moves)

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

#include <iostream>
#include <vector>

struct Move {
    int disk;
    char from;
    char to;
};

void hanoi(int n, char source, char target, char spare, std::vector<Move>& moves) {
    if (n == 0) {
        return;
    }
    hanoi(n - 1, source, spare, target, moves);
    moves.push_back({n, source, target});
    hanoi(n - 1, spare, target, source, moves);
}

int main() {
    std::vector<Move> moves;
    hanoi(3, 'A', 'C', 'B', moves);
    for (const auto& [disk, from, to] : moves) {
        std::cout << "Move disk " << disk << " from " << from << " to " << to << '\n';
    }
}

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.cpp and run it:

Run it

g++ -std=c++17 hanoi.cpp -o hanoi
./hanoi

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
1–2<iostream> to print, and <vector> because this version collects the moves instead of printing them as it goes — which is what you want the moment the caller has to do anything with them beyond reading them.
4–8A move is three plain values and nothing else, so a struct with no constructor is the honest description of it. Being an aggregate, it can be built straight from a braced list, which is what line 15 relies on.
10std::vector<Move>& — by reference, so that every call appends to one list. Passed by value instead, each call would fill in a copy that is discarded when it returns, and the program would print nothing at all.
11–13Recursion needs a size it can answer without calling itself, or it never stops. Zero disks is that size: no disk to move, so nothing to append.
14Disk n needs target clear when it arrives, so the n - 1 disks above it are sent to spare instead — the reason the last two tower arguments trade places on the way in. The towers go in by value and moves by reference: each call needs labels of its own, but every call has to append to the same list.
15The only line that records a move. Line 14 has just cleared everything smaller off both towers, which is what makes the move legal here and nowhere else. The braces build a Move in place.
16Line 14 mirrored. The disks are on spare now, so it is the source, and the tower they started on becomes the working space.
19–25Collecting first and printing second is the point of this version: main ends up holding the whole solution, and could count it, sort it or replay it on a board. The structured binding const auto& [disk, from, to] unpacks each Move without naming its fields again, and is why the listing needs C++17.

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.

Returning moves, not printing them

The recursive listing on every other page of this cluster prints each move the moment it is made. This one records them, and that small change is the idiomatic one in C++: the solver no longer decides what happens to its answer. main prints the list, but a test could compare it against an expected vector, and a GUI could animate it at its own pace.

The cost is memory the printing version never uses. The vector ends up holding all 2ⁿ − 1 moves, and a Move is 8 bytes on a typical platform — so twenty disks is about 8 MB, and thirty disks is about 8.6 GB. If you know the size up front, moves.reserve((1ULL << n) - 1) saves the vector from reallocating as it grows.

Counting the moves at compile time

count_moves.cpp

#include <cstdint>
#include <iostream>
#include <limits>

// The recurrence itself: T(0) = 0 and T(n) = 2T(n - 1) + 1. constexpr lets
// the compiler run it, and no step on the way to 64 disks overflows.
constexpr std::uint64_t minimum_moves(int n) {
    return n == 0 ? 0 : 2 * minimum_moves(n - 1) + 1;
}

// Checked while compiling: if either were false, this file would not build.
static_assert(minimum_moves(3) == 7);
static_assert(minimum_moves(64) == std::numeric_limits<std::uint64_t>::max());

int main() {
    constexpr std::uint64_t moves = minimum_moves(64);
    std::cout << "64 disks: " << moves << " moves\n";

    // Unsigned arithmetic wraps rather than overflowing: one move more is zero.
    std::cout << "one more: " << moves + 1 << '\n';
}

Run it

g++ -std=c++17 count_moves.cpp -o count_moves
./count_moves

Output

64 disks: 18446744073709551615 moves
one more: 0

constexpr lets the compiler run a function while it builds the program, and static_assert stops the build if what it finds is false. So this listing does more than print 2⁶⁴ − 1: it cannot be compiled at all unless minimum_moves(64) equals std::numeric_limits<std::uint64_t>::max(), the largest value an unsigned 64-bit integer holds. The function is the recurrence from the algorithm page, T(n) = 2T(n − 1) + 1, run by the compiler — and it never overflows on the way up, because 2 × (2⁶³ − 1) + 1 is exactly 2⁶⁴ − 1.

A constant expression also changes what a mistake costs. In C, (UINT64_C(1) << 64) - 1 is undefined behaviour that compiles and runs. Here, constexpr std::uint64_t moves = (std::uint64_t{1} << 64) - 1; does not compile: a compiler evaluating a constant is required to reject undefined behaviour it meets, and GCC reports that the shift is greater than or equal to the precision of its left operand. Drop the constexpr and the line is an ordinary run-time calculation again — exactly as undefined as it is in C, with a warning at best.

The last line of output is the one overflow that is not a mistake. Unsigned arithmetic is defined to wrap round modulo 2⁶⁴, so the maximum plus one is exactly 0. Signed overflow has no such guarantee: a signed std::int64_t stops one bit short of the count, at 2⁶³ − 1, and going past it is undefined.

Without recursion, three pairs of towers

hanoi_pairs.cpp

#include <array>
#include <cstdint>
#include <iostream>
#include <utility>
#include <vector>

using Towers = std::array<std::vector<int>, 3>;

// Plays the one legal move between towers a and b, whichever way round it goes.
void move_between(Towers& towers, int a, int b) {
    if (towers[a].empty() || (!towers[b].empty() && towers[b].back() < towers[a].back())) {
        std::swap(a, b);
    }
    int disk = towers[a].back();
    towers[a].pop_back();
    towers[b].push_back(disk);
    std::cout << "Move disk " << disk << " from " << "ABC"[a] << " to " << "ABC"[b] << '\n';
}

void hanoi_pairs(int n) {
    Towers towers;
    for (int disk = n; disk >= 1; disk--) {
        towers[0].push_back(disk);
    }
    // The same three pairs of towers, over and over. For an odd n, B and C trade places.
    const int b = n % 2 == 0 ? 1 : 2;
    const int c = 3 - b;
    const std::uint64_t total = (std::uint64_t{1} << n) - 1;
    for (std::uint64_t m = 0; m < total; m++) {
        switch (m % 3) {
            case 0: move_between(towers, 0, b); break;
            case 1: move_between(towers, 0, c); break;
            case 2: move_between(towers, b, c); break;
        }
    }
}

int main() {
    hanoi_pairs(3);
}

Run it

g++ -std=c++17 hanoi_pairs.cpp -o hanoi_pairs
./hanoi_pairs

This loop never decides which disk to move. It cycles through three pairs of towers — A and B, A and C, B and C for an even number of disks, with B and C trading places for an odd number — and between any two towers there is exactly one legal move, which move_between finds by comparing their top disks and swapping a and b if the move goes the other way. The loop bound is a std::uint64_t for the reason the section above gives: (std::uint64_t{1} << n) - 1 is well defined up to 63 disks, where the signed (1LL << 63) - 1 would overflow.

Those three repeated pairs are the smallest disk’s circuit seen from the outside: every third move involves each pair, and the pattern reproduces the recursive solution exactly. The iterative solution shows why, move by move on a board.

The same algorithm in other languages

Frequently asked questions

How do you implement Tower of Hanoi in C++?

Write a recursive function that takes the disk count, three tower names and a std::vector of moves by reference. If n is 0, return; otherwise recurse on n - 1 disks from source to spare, push_back the move of disk n, and recurse on n - 1 disks from spare to target. The vector then holds all 2ⁿ − 1 moves in order.

What is the iterative Tower of Hanoi algorithm in C++?

Keep the towers in a std::array of three vectors and loop 2ⁿ − 1 times, cycling through three pairs of towers: A and B, A and C, B and C for an even number of disks, with B and C swapped for an odd number. On each pass, make the one legal move between that pair, whichever direction it goes.

Does 2⁶⁴ − 1 fit in a C++ integer type?

Yes, exactly: it is std::numeric_limits<std::uint64_t>::max(), the largest value a 64-bit unsigned integer holds. A signed std::int64_t stops at 2⁶³ − 1. Adding one to the unsigned maximum wraps round to zero, which is well defined for unsigned types.

Can C++ compute the Tower of Hanoi move count at compile time?

Yes. Write the recurrence as a constexpr function returning std::uint64_t — n == 0 ? 0 : 2 * minimum_moves(n - 1) + 1 — and check it with static_assert(minimum_moves(64) == std::numeric_limits<std::uint64_t>::max()). The compiler evaluates it while building the program, and the build fails if the value is wrong.