Data Structures & Algorithms (DSA) Module 18 – Maths for DSA
Modular arithmetic and fast exponentiation
Reduce sums and products modulo m at every step, compute a^b mod m by repeated squaring, and find modular inverses with Fermat or extended Euclid.
What you will learn
- Apply the rules for modular addition, subtraction and multiplication
- Compute a^b mod m by repeated squaring in O(log b) multiplications
- Find modular inverses with Fermat's little theorem and with extended Euclid
- Normalise remainders of negative numbers in Python, Java, C++, Go and JavaScript
Before you start
What you’ll learn · Before you start
What you will learn
- Apply the rules for modular addition, subtraction and multiplication
- Compute a^b mod m by repeated squaring in O(log b) multiplications
- Find modular inverses with Fermat's little theorem and with extended Euclid
- Normalise remainders of negative numbers in Python, Java, C++, Go and JavaScript
Before you start
On this page
Modular arithmetic works with remainders. Two numbers are congruent modulo m, written a ≡ b (mod m), when m divides
a − b, so every whole number is equivalent to exactly one remainder from 0 to m − 1. Sums, differences and products
can be reduced modulo m after every step without changing the final remainder, which keeps the numbers small; that
is why so many problems ask for an answer modulo 10⁹ + 7. Division is different: dividing by a means multiplying by
its inverse, which exists exactly when gcd(a, m) = 1. Fast exponentiation computes a^b mod m with O(log b)
multiplications by squaring, one step per binary digit of b, and Python’s pow(a, b, m) does it built in.
Reduce after every step
Write a = q × m + r. Adding or multiplying the multiples of m only adds more multiples of m, so only the remainders matter: (a + b) mod m = ((a mod m) + (b mod m)) mod m, and the same holds for subtraction and multiplication. In modular arithmetic a long computation can therefore keep every intermediate value below m:
import math
P = 10**9 + 7
N = 100_000
exact = math.factorial(N)
running = 1
largest = 0
for i in range(1, N + 1):
running = running * i % P # reduce after every multiplication
largest = max(largest, running)
print(f"{N:,}! has {math.floor(math.log10(exact)) + 1:,} decimal digits")
print(f"exact value mod P: {exact % P}")
print(f"product reduced each step: {running}")
print(f"largest number kept while reducing: {largest:,} (always below P = {P:,})")
print(f"(a + b) mod P == ((a mod P) + (b mod P)) mod P for a = 3^500, b = 7^400: {(3**500 + 7**400) % P == (3**500 % P + 7**400 % P) % P}") Output
100,000! has 456,574 decimal digits exact value mod P: 457992974 product reduced each step: 457992974 largest number kept while reducing: 999,992,676 (always below P = 1,000,000,007) (a + b) mod P == ((a mod P) + (b mod P)) mod P for a = 3^500, b = 7^400: True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 running_product.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The exact factorial has 456,574 digits, but the running product, reduced after each multiplication, never held a number larger than 999,992,676 and gave the same remainder, 457,992,974. With m = 10⁹ + 7, the product of two remainders is below 10¹⁸ + 1.5 × 10¹⁰, inside the 64-bit range of about 9.22 × 10¹⁸, so compiled languages can multiply two remainders without overflow.
Negative numbers need care, because languages disagree about the sign of a remainder:
Python · sign/remainder.py
# Python's % takes the sign of the divisor, and // rounds down
for a, m in [(-7, 3), (7, -3), (-7, -3), (7, 3)]:
print(f"{a:>2} % {m:>2} = {a % m:>2} {a:>2} // {m:>2} = {a // m:>2}")
print("((-7 % 3) + 3) % 3 =", ((-7 % 3) + 3) % 3) Output
-7 % 3 = 2 -7 // 3 = -3 7 % -3 = -2 7 // -3 = -3 -7 % -3 = -1 -7 // -3 = 2 7 % 3 = 1 7 // 3 = 2 ((-7 % 3) + 3) % 3 = 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 remainder.py
JavaScript · sign/remainder.mjs
// JavaScript's % keeps the sign of the dividend; Math.trunc mirrors the division that goes with it
for (const [a, m] of [[-7, 3], [7, -3], [-7, -3], [7, 3]]) {
console.log(`${String(a).padStart(2)} % ${String(m).padStart(2)} = ${String(a % m).padStart(2)} trunc(${a} / ${m}) = ${Math.trunc(a / m)}`);
}
console.log('((-7 % 3) + 3) % 3 =', ((-7 % 3) + 3) % 3); Output
-7 % 3 = -1 trunc(-7 / 3) = -2 7 % -3 = 1 trunc(7 / -3) = -2 -7 % -3 = -1 trunc(-7 / -3) = 2 7 % 3 = 1 trunc(7 / 3) = 2 ((-7 % 3) + 3) % 3 = 2
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node remainder.mjs
Java · compiled/Remainder.java
public class Remainder {
public static void main(String[] args) {
// % takes the sign of the dividend and / rounds toward zero; Math.floorMod takes the sign of the divisor
int[][] pairs = {{-7, 3}, {7, -3}, {-7, -3}, {7, 3}};
for (int[] p : pairs) {
System.out.printf("%2d %% %2d = %2d %2d / %2d = %2d floorMod = %2d%n", p[0], p[1], p[0] % p[1], p[0], p[1], p[0] / p[1], Math.floorMod(p[0], p[1]));
}
}
} C++ · compiled/remainder.cpp
#include <cstdio>
int main() {
// / rounds toward zero, so % takes the sign of the dividend
const int pairs[][2] = {{-7, 3}, {7, -3}, {-7, -3}, {7, 3}};
for (const auto& p : pairs) {
std::printf("%2d %% %2d = %2d %2d / %2d = %2d\n", p[0], p[1], p[0] % p[1], p[0], p[1], p[0] / p[1]);
}
std::printf("((-7 %% 3) + 3) %% 3 = %d\n", ((-7 % 3) + 3) % 3);
} Go · compiled/remainder.go
package main
import "fmt"
func main() {
// / truncates toward zero, and x % y has the sign of x
pairs := [][2]int{{-7, 3}, {7, -3}, {-7, -3}, {7, 3}}
for _, p := range pairs {
fmt.Printf("%2d %% %2d = %2d %2d / %2d = %2d\n", p[0], p[1], p[0]%p[1], p[0], p[1], p[0]/p[1])
}
fmt.Printf("((-7 %% 3) + 3) %% 3 = %d\n", ((-7%3)+3)%3)
} Runs on this device, in your browser. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
from session import run, versions
# Remainder.java runs as a single source file, remainder.cpp is built with the sanitizers on, go run builds Go.
versions("java -version", "clang++ --version", "go version")
run("""
java -XX:-UsePerfData Remainder.java
clang++ -std=c++23 -O2 -Wall -Wextra -fsanitize=address,undefined remainder.cpp -o remainder && ./remainder
go run remainder.go
""") Output
# openjdk version "25.0.4.1" LTS # Apple clang version 21.0.0 (clang-2100.3.34.2) # go version go1.27.1 darwin/arm64 $ java -XX:-UsePerfData Remainder.java -7 % 3 = -1 -7 / 3 = -2 floorMod = 2 7 % -3 = 1 7 / -3 = -2 floorMod = -2 -7 % -3 = -1 -7 / -3 = 2 floorMod = -1 7 % 3 = 1 7 / 3 = 2 floorMod = 1 $ clang++ -std=c++23 -O2 -Wall -Wextra -fsanitize=address,undefined remainder.cpp -o remainder && ./remainder -7 % 3 = -1 -7 / 3 = -2 7 % -3 = 1 7 / -3 = -2 -7 % -3 = -1 -7 / -3 = 2 7 % 3 = 1 7 / 3 = 2 ((-7 % 3) + 3) % 3 = 2 $ go run remainder.go -7 % 3 = -1 -7 / 3 = -2 7 % -3 = 1 7 / -3 = -2 -7 % -3 = -1 -7 / -3 = 2 7 % 3 = 1 7 / 3 = 2 ((-7 % 3) + 3) % 3 = 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 remainder_session.py
Python’s % takes the sign of the divisor, so −7 % 3 is 2, the remainder of modular arithmetic. Java, C++, Go and
JavaScript round the quotient toward zero and give −1, the sign of the dividend. ((a % m) + m) % m turns either
answer into the range 0 to m − 1, and Java offers Math.floorMod.
Fast exponentiation by repeated squaring
Multiplying by a, b times, takes b − 1 multiplications, hopeless for b = 10¹⁸. Fast exponentiation uses the binary digits of b instead: 45 = 32 + 8 + 4 + 1, so a⁴⁵ = a³² × a⁸ × a⁴ × a. Square a again and again to get a, a², a⁴, a⁸ and so on, and multiply the result by each square whose digit is 1, reducing modulo m every time:
Repeated squaring for 7⁴⁵ mod 1000, one column per binary digit of 45
Text description of the diagram
The picture has six columns, one for each binary digit of 45 = 101101 in binary, read from the lowest digit on the left to the highest on the right.
- The top row holds the squares of 7 modulo 1000, each the square of the one before it: 7¹ = 7, 7² = 49, 7⁴ = 2401 mod 1000 = 401, 7⁸ = 801, 7¹⁶ = 601 and 7³² = 201.
- The middle row holds the digits of 45 from the lowest: 1, 0, 1, 1, 0, 1. The columns with a 1 are shaded, and their squares have a bold border: 7¹, 7⁴, 7⁸ and 7³², because 45 = 1 + 4 + 8 + 32.
- The bottom row holds the running result after each column, starting from 1: it becomes 7 after the first column, stays 7 after the second, becomes 7 × 401 mod 1000 = 807, then 807 × 801 mod 1000 = 407, stays 407, and ends at 407 × 201 mod 1000 = 807.
So 7⁴⁵ mod 1000 = 807, found with 5 squarings and 4 multiplications instead of 44 multiplications, and every number stays below 1000 × 1000.
def fast_pow(a, b, m):
"""a^b mod m by repeated squaring; also returns how many multiplications it made."""
result, multiplications = 1 % m, 0
a %= m
while b > 0:
if b & 1: # this binary digit of b is 1: multiply the current square in
result = result * a % m
multiplications += 1
b >>= 1
if b > 0: # square for the next binary digit
a = a * a % m
multiplications += 1
return result, multiplications
P = 10**9 + 7
print(f"7^45 mod 1000 = {fast_pow(7, 45, 1000)[0]}, Python's pow agrees: {fast_pow(7, 45, 1000)[0] == pow(7, 45, 1000)}")
print("exponent b binary digits ones multiplications 2^b mod P")
for b in (45, 10**9, 10**18):
value, count = fast_pow(2, b, P)
assert value == pow(2, b, P)
print(f"{b:<25,} {b.bit_length():>13} {bin(b).count('1'):>5} {count:>16} {value}") Output
7^45 mod 1000 = 807, Python's pow agrees: True exponent b binary digits ones multiplications 2^b mod P 45 6 4 9 371842544 1,000,000,000 30 13 42 140625001 1,000,000,000,000,000,000 60 24 83 719476260
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 fast_pow.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The loop makes one squaring per binary digit and one more multiplication per digit that is 1. For b = 10¹⁸, with 60
binary digits of which 24 are 1, that is 83 multiplications, and the result matches pow(2, 10**18, P). Fast
exponentiation therefore costs O(log b) multiplications, and with reduction every number in it stays below m².
Dividing by finding an inverse
The inverse of a modulo m is the residue x for which a × x leaves remainder 1, so multiplying by x undoes a multiplication by a. The lesson on Euclid’s algorithm showed when it exists: the extended algorithm gives a·x + m·y = gcd(a, m), so an inverse exists exactly when gcd(a, m) = 1. For a prime m there is a shortcut, Fermat’s little theorem: a^(m − 1) ≡ 1, so a^(m − 2) is the inverse, one fast exponentiation away.
P = 10**9 + 7 # a prime
def extended_gcd(a, b):
old_r, r, old_s, s = a, b, 1, 0
while r != 0:
q = old_r // r
old_r, r = r, old_r - q * r
old_s, s = s, old_s - q * s
return old_r, old_s # gcd(a, b) and x with a·x + b·y = gcd(a, b)
g, x = extended_gcd(7, P)
print("inverse of 7 modulo P:")
print(f" by Fermat, 7^(P - 2) mod P = {pow(7, P - 2, P)}")
print(f" by extended Euclid = {x % P}")
print(f" by pow(7, -1, P) = {pow(7, -1, P)}")
print(f" check: 7 × {pow(7, -1, P)} mod P = {7 * pow(7, -1, P) % P}")
values = [3, 4, 4] # their average is 11/3, not a whole number
residue = sum(values) % P * pow(len(values), -1, P) % P
print(f"average of {values} as a residue: {residue}; times 3 it gives back {residue * 3 % P} = 11")
for a, m in [(2, 9), (2, 10)]:
try:
print(f"pow({a}, -1, {m}) = {pow(a, -1, m)}")
except ValueError as error:
print(f"pow({a}, -1, {m}) raises ValueError: {error}") Output
inverse of 7 modulo P: by Fermat, 7^(P - 2) mod P = 142857144 by extended Euclid = 142857144 by pow(7, -1, P) = 142857144 check: 7 × 142857144 mod P = 1 average of [3, 4, 4] as a residue: 666666675; times 3 it gives back 11 = 11 pow(2, -1, 9) = 5 pow(2, -1, 10) raises ValueError: base is not invertible for the given modulus
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 inverses.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
All three methods give 142,857,144, and 7 × 142,857,144 = 1,000,000,008 leaves remainder 1. A division such as an
average becomes a multiplication by an inverse: 11 / 3 is the residue 666,666,675, and 3 times it gives back 11.
pow(a, -1, m), available since Python 3.8, finds the inverse modulo any m, and 2 has none modulo 10.
| Operation | Rule modulo m | Watch for |
|---|---|---|
| a + b, a − b | Reduce each result | A negative difference in Java, C++, Go and JavaScript |
| a × b | Reduce each product | Products above 2⁶³ when m is above about 3 × 10⁹ |
| a^b | Repeated squaring, O(log b) | Reduce after every multiplication |
| a / b | Multiply by the inverse of b | It exists only when gcd(b, m) = 1 |
Three more tools build on these rules. Matrix exponentiation applies repeated squaring to a matrix and computes a term of a linear recurrence, such as Fibonacci(10¹⁸), in O(log n) matrix products. 128-bit products keep a × b mod m exact when m is near 2⁶³. A table of inverses finds every inverse from 1 to n in one pass.
Exercise and self-check
Write the squaring loop and a modular inverse that also works when the modulus is not prime.
Exercise · Medium · Python, JavaScript
Fast power and modular inverses
Write two functions.
pow_mod(a, b, m)(JavaScript:powMod) returnsato the powerb, modulom, for whole numbersa >= 0,0 <= b <= 10¹⁸and1 <= m <= 10¹⁸, with repeated squaring. Count 0⁰ as 1, as Python'spowdoes, so the answer forb = 0is1 % m.inverse_mod(a, m)(JavaScript:inverseMod) returns thexwith0 <= x < manda * xleaving remainder 1 modulom, fora >= 0and2 <= m <= 10¹⁸, orNone(JavaScript:null) when there is no suchx. The modulus need not be prime.
In JavaScript every argument and result is a BigInt (7n, 1000n), because products of numbers near 10¹⁸ are far past the exact integers of a Number. The sample tests check small cases, exponents near 10¹⁸, a few hundred random cases against Python's pow (or exact BigInt arithmetic), inverses for every small modulus, and moduli that are not prime.
Python · Starter code · modpow.py
def pow_mod(a, b, m):
"""a to the power b, modulo m, by repeated squaring (0 to the power 0 counts as 1)."""
raise NotImplementedError("write pow_mod")
def inverse_mod(a, m):
"""The x with 0 <= x < m and a * x % m == 1, or None when there is none."""
raise NotImplementedError("write inverse_mod") The sample tests · test_modpow.py
import math
import random
from modpow import inverse_mod, pow_mod
P = 10**9 + 7
def test_small_powers():
"""small cases, including a base larger than the modulus and exponent 0"""
assert pow_mod(7, 45, 1000) == 807
assert pow_mod(2, 10, 1000) == 24
assert pow_mod(10, 3, 7) == 6
assert pow_mod(5, 0, 7) == 1
assert pow_mod(0, 0, 7) == 1
assert pow_mod(5, 0, 1) == 0
assert pow_mod(0, 5, 7) == 0
def test_exponents_near_ten_to_the_eighteen():
"""exponents up to 10^18, a prime modulus and a modulus near 10^18"""
assert pow_mod(2, 10**18, P) == 719476260
assert pow_mod(3, 10**18 - 1, 10**18 + 9) == pow(3, 10**18 - 1, 10**18 + 9)
assert pow_mod(10**18 + 8, 2, 10**18 + 9) == 1
def test_random_powers():
"""500 random cases compared with Python's pow"""
rng = random.Random(21)
for _ in range(500):
a, b, m = rng.randrange(0, 10**18), rng.randrange(0, 10**18), rng.randrange(1, 10**18)
assert pow_mod(a, b, m) == pow(a, b, m), (a, b, m)
def test_small_inverses():
"""inverses that exist, and two that do not"""
assert inverse_mod(3, 7) == 5
assert inverse_mod(10, 7) == 5
assert inverse_mod(7, P) == 142857144
assert inverse_mod(2, 9) == 5
assert inverse_mod(1, 2) == 1
assert inverse_mod(2, 10) is None
assert inverse_mod(6, 9) is None
assert inverse_mod(0, 5) is None
def test_every_small_modulus():
"""every a below m for every m from 2 to 60, against a search"""
for m in range(2, 61):
for a in range(m):
found = next((x for x in range(m) if a * x % m == 1), None)
assert inverse_mod(a, m) == found, (a, m)
def test_moduli_that_are_not_prime():
"""Fermat's a^(m - 2) fails here; the extended algorithm does not"""
m = 10**18
for a in (999_999_999_999_999_999, 123_456_789_012_345_677, 3):
x = inverse_mod(a, m)
assert x is not None and 0 <= x < m and a * x % m == 1
assert inverse_mod(10**9, m) is None
rng = random.Random(8)
for _ in range(300):
m = rng.randrange(2, 10**12)
a = rng.randrange(0, m)
x = inverse_mod(a, m)
if math.gcd(a, m) == 1:
assert x == pow(a, -1, m), (a, m)
else:
assert x is None, (a, m) JavaScript · Starter code · modpow.mjs
/** a to the power b, modulo m, by repeated squaring (0 to the power 0 counts as 1). All BigInts. */
export function powMod(a, b, m) {
throw new Error('write powMod');
}
/** The x with 0 <= x < m and a * x % m === 1, or null when there is none. All BigInts. */
export function inverseMod(a, m) {
throw new Error('write inverseMod');
} The sample tests · modpow.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { inverseMod, powMod } from './modpow.mjs';
const P = 1000000007n;
/** Exact a^b mod m with BigInt, by plain repeated squaring of a different shape (right to left over a bit string). */
function referencePow(a, b, m) {
let result = 1n % m;
const bits = b.toString(2);
for (const bit of bits) {
result = (result * result) % m;
if (bit === '1') result = (result * a) % m;
}
return result;
}
/** A small seeded generator of BigInts below limit (a 64-bit linear congruential generator). */
function generator(seed) {
let state = BigInt(seed);
return (limit) => {
state = (state * 6364136223846793005n + 1442695040888963407n) % (1n << 64n);
return state % limit;
};
}
function gcd(a, b) {
while (b) [a, b] = [b, a % b];
return a;
}
test('small cases, including a base larger than the modulus and exponent 0', () => {
assert.equal(powMod(7n, 45n, 1000n), 807n);
assert.equal(powMod(2n, 10n, 1000n), 24n);
assert.equal(powMod(10n, 3n, 7n), 6n);
assert.equal(powMod(5n, 0n, 7n), 1n);
assert.equal(powMod(0n, 0n, 7n), 1n);
assert.equal(powMod(5n, 0n, 1n), 0n);
assert.equal(powMod(0n, 5n, 7n), 0n);
});
test('exponents up to 10^18, a prime modulus and a modulus near 10^18', () => {
assert.equal(powMod(2n, 10n ** 18n, P), 719476260n);
assert.equal(powMod(10n ** 18n + 8n, 2n, 10n ** 18n + 9n), 1n);
});
test('300 random cases compared with exact BigInt arithmetic', () => {
const next = generator(21);
for (let i = 0; i < 300; i++) {
const a = next(10n ** 18n);
const b = next(10n ** 18n);
const m = next(10n ** 18n - 1n) + 1n;
assert.equal(powMod(a, b, m), referencePow(a % m, b, m), `${a} ${b} ${m}`);
}
});
test('inverses that exist, and two that do not', () => {
assert.equal(inverseMod(3n, 7n), 5n);
assert.equal(inverseMod(10n, 7n), 5n);
assert.equal(inverseMod(7n, P), 142857144n);
assert.equal(inverseMod(2n, 9n), 5n);
assert.equal(inverseMod(1n, 2n), 1n);
assert.equal(inverseMod(2n, 10n), null);
assert.equal(inverseMod(6n, 9n), null);
assert.equal(inverseMod(0n, 5n), null);
});
test('every a below m for every m from 2 to 60, against a search', () => {
for (let m = 2n; m <= 60n; m++) {
for (let a = 0n; a < m; a++) {
let found = null;
for (let x = 0n; x < m; x++) if ((a * x) % m === 1n) { found = x; break; }
assert.equal(inverseMod(a, m), found, `${a} ${m}`);
}
}
});
test("Fermat's a^(m - 2) fails here; the extended algorithm does not", () => {
const m = 10n ** 18n;
for (const a of [999999999999999999n, 123456789012345677n, 3n]) {
const x = inverseMod(a, m);
assert.ok(x !== null && x >= 0n && x < m && (a * x) % m === 1n, `${a}`);
}
assert.equal(inverseMod(10n ** 9n, m), null);
const next = generator(8);
for (let i = 0; i < 200; i++) {
const mod = next(10n ** 12n - 2n) + 2n;
const a = next(mod);
const x = inverseMod(a, mod);
if (gcd(a, mod) === 1n) assert.ok(x !== null && (a * x) % mod === 1n && x < mod, `${a} ${mod}`);
else assert.equal(x, null, `${a} ${mod}`);
}
}); A hint
For pow_mod, keep result and the current square of a; at every binary digit of b, multiply the square into result when the digit is 1, then square it, and reduce modulo m after each multiplication. For inverse_mod, run the extended Euclidean algorithm on a and m: the inverse exists exactly when their GCD is 1, and then it is the coefficient of a, reduced modulo m.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Why the rules hold: the squaring loop and Fermat's theorem proved Pro: opens with a Pro, Premium or Ultimate pass
Every rule above has a short proof, and each proof also says where the rule stops working. This part proves the reduction rules, gives the invariant behind the squaring loop with a dry run, proves Fermat’s little theorem, and builds every inverse from 1 to 10⁶ in one pass.
In this part:
- Reduction never changes the remainder: the proof
- The invariant of the squaring loop, with a dry run
- Fermat’s little theorem, proved by rearranging remainders
- Every inverse from 1 to n in one pass
1 runnable example (Python) · about 2 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Products near 2⁶³: the overflow reproduced and fixed in five languages Pro: opens with a Pro, Premium or Ultimate pass
With a modulus near 10¹⁸, the product of two remainders needs about 120 bits, and every fixed-width language gets it wrong in its own way. This part reproduces the failure with one product whose answer is known, and fixes it with the right tool in each language.
In this part:
- One product, five wrong or right answers
- What each fix costs
- Tests that catch a wrong product
2 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Matrix exponentiation for linear recurrences, with a practice set Pro: opens with a Pro, Premium or Ultimate pass
Repeated squaring works for anything that can be multiplied associatively, matrices included. This part computes Fibonacci(10¹⁸) modulo 10⁹ + 7 with 2 × 2 matrices, checks it with fast doubling, solves a three-term recurrence and measures when the k³ factor of matrix exponentiation becomes too large.
In this part:
- Fibonacci(10¹⁸) with 2 × 2 matrices, checked by fast doubling
- A three-term recurrence: climbing stairs 1, 2 or 3 at a time
- When k³ log n is too much, with numbers
- Practice set
1 runnable example (Python) · about 2 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher · Warm-up
Why do so many programming problems ask for the answer modulo 10⁹ + 7?
The exact answers, such as counts of paths or arrangements, often have thousands of digits, while a remainder modulo a fixed number is small and can still be checked exactly. 10⁹ + 7 suits the job for three reasons. It is prime, so every number from 1 to 10⁹ + 6 has an inverse and division works through Fermat's little theorem. Every remainder, and the sum of two of them, fits in a signed 32-bit integer. And the product of two remainders stays below about 1.00000001 × 10¹⁸, inside the 64-bit range of about 9.22 × 10¹⁸, so multiplying and then reducing cannot overflow in Java, C++ or Go. 998,244,353 is another common choice: it is prime too, and one less than it is 119 × 2²³, a shape that number-theoretic transforms need.
-
Mid
A Java service computes (a * b) % m on long values with m = 10¹² + 39. Some results are negative, and others are wrong but look normal. Explain both, and give two fixes with their costs.
-
Fresher to mid
A problem asks for the average of n values as a residue modulo 10⁹ + 7, that is, the sum times the inverse of n. How do you compute it, what goes wrong if n is a multiple of the modulus, and what changes if the modulus is 10⁹?
-
Senior
Compute the n-th term of a linear recurrence of order k modulo a prime, for n up to 10¹⁸. What does matrix exponentiation cost, when does it become too slow, and what do you use then?
Key takeaways
- Reduce after every addition, subtraction and multiplication; the remainder at the end is the same.
- Python’s % takes the sign of the divisor; Java, C++, Go and JavaScript take the sign of the dividend, so normalise
with ((a % m) + m) % m or
Math.floorMod. - Fast exponentiation squares once per binary digit of b: 83 multiplications for b = 10¹⁸.
- An inverse modulo m exists exactly when gcd(a, m) = 1; for a prime m it is a^(m − 2), and
pow(a, -1, m)finds it for any m.
References
- The Python Language Reference: binary arithmetic operations (Python Software Foundation)
- Built-in functions: pow() (Python Software Foundation)
- Class Math (Java SE 25): floorMod, multiplyHigh (Oracle)
- Class BigInteger (Java SE 25): mod, modPow, modInverse (Oracle)
- Arithmetic operators: multiplicative operators (cppreference.com)
- The Go Programming Language Specification: arithmetic operators (The Go Authors)
- Package math/bits: Mul64 and Rem64 (The Go Authors)
- Remainder (%) (MDN Web Docs)
- ECMAScript 2026 Language Specification: BigInt (Ecma International)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress