A senior engineer once asked me to review a hash-table implementation a junior had written. The hash function looked fine, the table looked fine, the test suite passed. Then they ran it against a production-scale dataset of strings that occasionally hashed to negative integers (the language was Java, where hashCode() returns a signed int), and the table exploded with ArrayIndexOutOfBoundsException. The bug was the index calculation: arr[hash % capacity]. In Java, -7 % 3 is -1, not 2. Negative indices are not what arrays want.
That bug is the small example of a much bigger pattern. Modular arithmetic shows up in working code more than I think. Hash tables, circular buffers, ring-shaped indexing, time-zone arithmetic, cyclic rotations, hash collision resolution, cryptography, big-number reductions, the modular form of Kadane-like algorithms. Every time, the same handful of laws decide whether the code is right, and every time the same negative-number trap and the same modular-inverse confusion catch people. I want to walk through the parts that have actually mattered to me in production code.
The four laws that cover almost all of it
Modular arithmetic is what happens to numbers when you only care about their remainder modulo some m. Two numbers are "equivalent mod m" if their difference is a multiple of m. So 7 ~ 1 (mod 6) because 7 - 1 = 6 is a multiple of 6.
The four facts that handle most working-engineer use cases:
(a + b) mod m = ((a mod m) + (b mod m)) mod m(a - b) mod m = ((a mod m) - (b mod m)) mod m(a * b) mod m = ((a mod m) * (b mod m)) mod m(a^k) mod m = ?(cannot just doa mod mand exponentiate; needs care, see below)
The first three say modulo distributes over addition, subtraction, and multiplication. You can take the mod whenever you want, as long as you take it again at the end, and the answer is the same as if you had done all the arithmetic and modded once at the end. This is what lets you keep all your intermediate values small, which is essential for big-number computations and for staying inside fixed-width integer ranges.
The fourth one (exponentiation) is more subtle. Naively a^k can overflow long before you take the mod, but you can interleave: square, mod, square, mod. That is the basis of fast modular exponentiation, which is what RSA and similar protocols rely on.
The negative-number trap, in three languages
Different languages disagree on what (-7) mod 3 should be. The mathematical convention is that the result lives in [0, m), so the answer is 2. Most modern languages disagree.
Python is the outlier; it returns the mathematical mod. JavaScript, Java, C, C++, Go, and Rust all follow the "sign of dividend" convention, which gives you a negative result when the dividend is negative.
This is the bug from the opening anecdote. If you index an array with arr[hash % capacity] in any language other than Python, you will get a negative index whenever the hash is negative, and the program will crash. The fix is to write a true mathematical mod:
The double-mod is necessary: (a % m) can be negative, adding m makes it positive but possibly out of range (if a % m was already positive), and the final % m brings it back into [0, m). Three operations, always correct.
I keep this mod helper in my common utilities file in every language that does not have it built in. The hash-table bug, the off-by-one in a circular buffer rotation, the time-zone calculation that runs an hour late once a day for the user in Anchorage; all three are different costumes for the same trap.
A worked example: a circular buffer
The clean canonical use of modular arithmetic is a fixed-size circular buffer that overwrites its oldest entries. Indexing forward and backward both rely on mod.
The (self.head - 1 - i) % self.capacity line is the part that breaks in non-Python. In Java or JS, when head - 1 - i goes negative, the index goes negative too, and the buffer access crashes. The Python version works because Python's % returns a positive value in [0, m). In any other language, you wrap with ((a % m) + m) % m.
Modular exponentiation, in code
This is the algorithm that makes RSA possible and that I have used directly in a few hash-related bits of code. The naive computation a^k mod m overflows for any reasonable k. The fast algorithm uses repeated squaring with mod at each step.
The trick: walk the bits of k from low to high. Each bit decides whether to multiply the running result by the current a. After each step, square a (which corresponds to the next bit position). Mod everything after every multiply. Because each multiply is (a * b) mod m and both a and b are already reduced, the operands are bounded by m, so the intermediate value is at most m^2. As long as m^2 fits in your integer type, you are safe.
Total time: O(log k) multiplications. For RSA-sized exponents (2048 bits), this is the difference between "instant" and "heat death of the universe".
Modular inverses (where it stops being obvious)
Here is where modular arithmetic genuinely diverges from regular arithmetic. In regular arithmetic, division is the inverse of multiplication: a * (1/a) = 1. In modular arithmetic, "1/a mod m" is a number x such that a * x ~ 1 (mod m). That x is the modular inverse of a.
The catch: not every a has an inverse mod m. The inverse exists if and only if gcd(a, m) = 1 (a and m are coprime). If m is prime, every nonzero a has an inverse.
To compute the inverse, two options. If m is prime, Fermat's Little Theorem says a^(m-2) ~ a^(-1) (mod m). So mod_pow(a, m - 2, m) is the inverse. If m is not prime (and gcd(a, m) = 1 still holds), you need the extended Euclidean algorithm.
I have only ever used modular inverses in two contexts: implementing modular fractions for combinatorics problems where I needed to compute n choose k mod p for a prime p, and reading other people's cryptography code. Outside those, the modular inverse is not something I reach for.
Where I actually use modular arithmetic in production
Listing the places it has come up for me, ranked by frequency.
Most common: index wrapping in fixed-size structures. Circular buffers, ring-based scheduler queues, hash tables (with hash % capacity). Every one of these is a modular-arithmetic operation, and every one of these has the negative-number trap in non-Python languages.
Common: time and calendar arithmetic. "What day of the week is N days from today?" is (today + N) mod 7. "What hour is it after a 50-hour shift?" is (start + 50) mod 24. Time zone offsets and DST add subtleties, but the underlying math is mod arithmetic on small numbers.
Common: hash collision resolution (linear probing). Walking forward through a hash table to find an empty slot uses (start + step) % capacity as the next index. Same trap.
Occasional: rolling hashes. Rabin-Karp string search and similar techniques compute a rolling polynomial hash. The hash is taken modulo a large prime, and updating the hash as the window slides involves a subtraction that requires the negative-handling trick.
Rare: combinatorial problems. Computing n choose k mod p for large n, where the only way to avoid overflow is to do all arithmetic mod p, including division (which requires modular inverses).
Rare but high-stakes: cryptography. RSA, Diffie-Hellman, ECDSA all rely on modular arithmetic. Most engineers should not roll their own crypto, but reading the code of a library you depend on is a good exercise, and it is all modular-arithmetic gymnastics.
"Mod is just remainder" is the lie that bites you
The phrase that gets juniors into trouble is "mod is just remainder". It is true in a very limited sense: in math, a mod m is defined to be the non-negative remainder. But in C, JavaScript, Java, and most other languages, the % operator does not implement the mathematical definition. It implements truncated division, which means the sign of the result matches the sign of the dividend. So (-7) % 3 is -1, not 2, and the array index that should have been 2 is now -1 and the program crashes. The "mod is just remainder" mental shortcut quietly hides this difference, and engineers who learned modulo in math class before they learned it in code carry the wrong intuition into production. The fix is mechanical: every time you take a % m and the result is going to be used as a non-negative quantity (an array index, a count, a position in a buffer), wrap it with ((a % m) + m) % m. The four laws (distributivity over +, -, *, plus careful exponentiation) cover the rest of what you actually need. Modular arithmetic is genuinely useful, it is genuinely simple once the language traps are understood, and it is genuinely a class of bugs you will write once and then never write again, but only after the first time it bites.
