Fast Modular Exponentiation

Try to compute 3^{1{,}000{,}000} \bmod 7 the obvious way — multiply by 3 a million times, reducing at the end — and you'll be sitting there long after the sun burns out. A computer isn't fast enough to save you either: a million multiplications of gigantic numbers is still a lot of work.

And yet there's a trick that gets the exact same answer in about twenty steps. It's called square-and-multiply (or repeated squaring), and it's not a curiosity — it's the single piece of arithmetic that makes modern cryptography computationally possible at all. Every time your browser sets up a secure connection, a chip somewhere is running this exact algorithm on numbers hundreds of digits long, and finishing before you'd notice the delay.

Systems like RSA are built entirely out of computations that look like m^{e} \bmod n, where m is a message, e is a key, and n is a large product of primes. If raising a number to a power took time proportional to the exponent, none of this would work — the keys involved are chosen precisely because they're huge, which is what keeps an eavesdropper from guessing them. The only reason huge exponents are usable at all is that square-and-multiply turns "proportional to the exponent" into "proportional to the number of digits of the exponent" — a difference that, at cryptographic scale, is the difference between milliseconds and forever.

The 20 steps for a million-sized exponent isn't a rough guess pulled out of thin air: 2^{20} = 1{,}048{,}576, which is just over a million, so an exponent that size needs about 20 bits to write in binary — and, as you'll see below, that's exactly how many squarings the algorithm needs.

The trick: double the exponent, don't count up to it

The slow way climbs the exponent one step at a time: m, m^2, m^3, m^4, \ldots — reaching m^{e} costs e - 1 multiplications. The fast way climbs by squaring, which doubles the exponent at every step instead of just adding one to it:

m \;\to\; m^2 \;\to\; m^4 \;\to\; m^8 \;\to\; m^{16} \;\to\; \cdots

After only k squarings you already hold m^{2^k} — exponent 2^{20}, over a million, needs just 20 squarings. But most exponents aren't neat powers of two. The fix is to write the exponent e in binary, which is exactly a sum of powers of two, and then combine only the squarings you need:

That's the whole algorithm: about \log_2 e squarings, plus one extra multiplication for each 1-bit of e.

Why does writing e in binary help? Because binary is exactly the statement "every whole number is a sum of distinct powers of two, in exactly one way" — the same fact behind a classic weighing puzzle: with just one weight each of 1, 2, 4, 8, 16, \ldots grams, you can balance any whole number of grams by choosing which weights to use. Here the "weights" are the squarings m^1, m^2, m^4, m^8, \ldots, and the bits of e tell you which ones to place on the scale. In computer-science terms, the running time has gone from O(e) — proportional to the exponent itself — down to O(\log e) — proportional to its number of digits.

Worked example: tracing $3^{13} \bmod 7$ step by step

First, 13 in binary is 1101_2 = 8 + 4 + 1, i.e. the bits (left to right) are 1, 1, 0, 1. Start the running result at 1 and process each bit, reducing mod 7 every time:

Done: 3^{13} \equiv 3 \pmod 7. That took four squarings and three extra multiplications — seven small modular multiplications in total, instead of the twelve a naive climb (13 - 1) would need. Every intermediate value along the way stayed a single digit — nothing ever got bigger than 36.

A second example, the other way round

You can also build the powers-of-two table first and multiply in the pieces afterwards — the same idea, organised right-to-left. For 7^{13} \bmod 11, with 13 = 8 + 4 + 1:

7^1\equiv 7,\ \ 7^2\equiv 5,\ \ 7^4\equiv 5^2 = 25\equiv 3,\ \ 7^8\equiv 3^2 = 9 \pmod{11} 7^{13} = 7^{8}\cdot 7^{4}\cdot 7^{1} \equiv 9 \cdot 3 \cdot 7 = 189 \equiv 2 \pmod{11}.

Same ingredients — repeated squaring plus picking out the bits that are 1 — just assembled in a different order. Whichever way you organise the bookkeeping, the number of modular multiplications stays around \log_2 e.

This "table of squarings, then multiply in the right ones" style is handy when you're working by hand, since you can compute the whole table first and then simply pick out which entries to multiply. The left-to-right version from the previous example is what a computer actually runs, because it needs to keep only one running number in memory rather than a whole table of powers — a small but real saving when the numbers involved are hundreds of digits long.

Just how big is the speed-up?

The naive method needs e - 1 multiplications; square-and-multiply needs roughly 2\log_2 e (the squarings, plus at most as many multiplications for the 1-bits). The gap explodes as e grows:

This single efficient algorithm is what makes Fermat's little theorem, Diffie–Hellman key exchange, RSA, and primality testing all usable in practice rather than just in theory.

Here it is as code — the whole algorithm fits in a dozen lines, and it's a piece of arithmetic old enough to predate computers by millennia: versions of "square repeatedly, multiply in the marked steps" show up in ancient Indian and Egyptian mathematics, long before anyone had a use for it in secret messages sent over wires. Run it below and watch it return 3^{1{,}000{,}000} \bmod 7 instantly.

function modPow(base: number, exp: number, mod: number): number { let result = 1; base = base % mod; while (exp > 0) { if (exp % 2 === 1) { result = (result * base) % mod; // multiply in this bit } exp = Math.floor(exp / 2); base = (base * base) % mod; // square, reducing every time } return result; } console.log(modPow(3, 13, 7)); // matches our hand trace: 3 console.log(modPow(3, 1000000, 7)); // instant — naively this needs a million steps

Putting it together

The whole idea rests on three small observations stacked on top of one another: squaring doubles an exponent instead of just incrementing it; any exponent can be built by adding together powers of two, because that's what its binary representation is; and reducing modulo n at every single step keeps every number involved small, however colossal the final exponent gets. Individually, none of the three is a deep idea — but combined, they turn a computation that should be impossible into one that finishes before you can blink.

The whole point of the algorithm collapses if you skip the "reduce every step" rule. Suppose you try to be clever and only reduce mod n once, right at the end, after computing the full power. For a small exponent like 13 that's already risky — 3^{13} = 1{,}594{,}323 — but for the exponent in our hook, 3^{1{,}000{,}000} has roughly 477{,}000 decimal digits before you ever get to reduce it. You'd need to store and multiply numbers far bigger than anything a calculator, or even most computers, can comfortably hold — all to throw almost all of that size away at the very last step.

Reducing mod n after every squaring and multiplication keeps every intermediate value smaller than n^2, no matter how huge the final exponent is. That's not a nice-to-have optimisation — it's the reason the algorithm is fast at all.

It's running on your device this very moment, more often than you'd guess. Every time you open a website whose address starts with https://, your browser and the server perform a handshake that relies on modular exponentiation with numbers 2048 or 4096 bits long — hundreds of digits. Doing that naively, one multiplication at a time, would take longer than the age of the universe for a single connection.

Because of square-and-multiply (and cleverer relatives of it), that same handshake finishes in milliseconds, and it happens billions of times a day, on everything from banking apps to the page you're reading right now. A trick you can trace by hand on a scrap of paper turns out to be quietly holding up the entire secure internet.

It isn't only cryptography, either. The very same "square the state, and multiply in when a bit is set" pattern is used to jump straight to the millionth term of a Fibonacci-like sequence, to raise big matrices to big powers in one swoop, and to speed up all sorts of computations built out of repeated multiplication. Once you've learned the trick once, you start spotting places to use it everywhere.