Why can you reduce mod m after each add and multiply in a running checksum, but not after a divide?
answer
- which operations preserve congruence?
- add, subtract and multiply are safe
- one operation has no meaning on residues
- you need an inverse, not a quotient
- try 6 divided by 2 with modulus 4
basics
~20 sCongruence survives addition, subtraction and multiplication, so reducing after each of those is exact. Division is not an operation on residues: it needs a modular inverse, which exists only when the divisor shares no factor with the modulus.
solid answer
~50 sReduction is a ring homomorphism for `+`, `-` and `*`: if you replace either operand by its residue, the result's residue is unchanged, which is what lets a streaming fold keep every intermediate below the modulus in a single pass. Division has no such guarantee, because a residue class does not remember which representative it came from. Concretely with `m = 4`: reducing 6 gives 2, and `2 / 2 = 1`, but the true `6 / 2 = 3`, whose residue is 3. The recovery is not truncating division but multiplication by a modular inverse — the value `b_inv` with `b * b_inv ≡ 1 (mod m)` — and that inverse exists only when `b` shares no factor with `m`. Choosing a prime modulus makes every nonzero residue invertible, which is why counting pipelines pick one.
code
pseudocode · 11 linesM = 1000000007
total = 0
product = 1
for i in 0..n-1
c = chunkId[i] mod M
total = (total + c) mod M
product = (product * c) mod M
...
// safe: both folds stay congruent to the unreduced result
// unsafe: this is not the mean of the chunk ids
mean = (total / n) mod Mgo deeper
Remember the safe list: addition, subtraction and multiplication can be reduced at any point; division cannot. Recognizing a divide inside a modular fold as suspicious is most of the value here.
Explain why: reduction collapses many values into one class, and addition and multiplication give the same output class for every representative while division does not. Have a small counterexample ready.
Demonstrate the review instincts — averaging a modular accumulator, sorting residues, folding an unreduced input — and know that the fix for division is multiplication by an inverse, with an existence condition you can state.
Frame it as a contract: decide once whether a pipeline's stored values are residues or true magnitudes, name it in the type or the field, and forbid comparison and division on the residue side rather than trusting each author to remember.
## The streaming setting An ingestion pipeline receives chunks and must fold each chunk's identifier into a running checksum. The constraints are the ones streaming always imposes: one pass, no buffering of the whole stream, and a fixed amount of state. Modular arithmetic is what makes that possible — the running value is kept reduced, so the state is a single machine word regardless of how many chunks arrive, and no arbitrary-precision arithmetic is needed. That works because reduction *commutes* with the operations the fold uses. ## What "distributes" actually means here The precise statement is about congruence. Write `a ≡ a' (mod m)` when `a` and `a'` leave the same remainder. Then: - if `a ≡ a'` and `b ≡ b'`, then `a + b ≡ a' + b'` - if `a ≡ a'` and `b ≡ b'`, then `a - b ≡ a' - b'` - if `a ≡ a'` and `b ≡ b'`, then `a * b ≡ a' * b'` So replacing any operand by its residue at any point leaves the final residue untouched. This is an exact algebraic identity, not an approximation, and it is what licenses reducing inside the loop rather than at the end. The structure has a name — the residues under these three operations form a ring — and the three operations listed are exactly the ring operations. Division is conspicuously absent. ## Why division falls out Reduction throws information away: `6`, `10`, `14` and `2` are all the same residue mod 4. Addition and multiplication are insensitive to that loss — every representative of a class produces an answer in the same output class. Division is not. Take `m = 4`: - `6 mod 4 = 2`, and truncating `2 / 2 = 1`. - But `6 / 2 = 3`, and `3 mod 4 = 3`. One is 1, the other is 3. The answer depended on which representative you started from, so the operation is not defined on residue classes at all. Picking a prime modulus does not rescue truncating division either: with `m = 5`, `6 mod 5 = 1`, and truncating `1 / 2 = 0`, while `6 / 2 = 3` reduces to 3. ## What replaces division Dividing by `b` becomes multiplying by `b`'s **modular inverse** — the residue `b_inv` satisfying `b * b_inv ≡ 1 (mod m)`. Then `a * b_inv` behaves as `a / b` does, and because it is a multiplication it slots back into the safe list above. The inverse does not always exist. It exists exactly when `b` and `m` share no common factor greater than 1. With `m = 4`, the residue 2 has no inverse: `2 * x` is always even, never 1 mod 4. With a **prime** modulus every nonzero residue is coprime to it, so every nonzero residue has an inverse — which is the practical reason counting pipelines and checksums standardize on a prime modulus rather than a round one. Even then, zero has no inverse, so dividing by a value congruent to zero is still undefined, and code must handle that case rather than assuming primality made it disappear. ## Two more things that do not survive reduction **Ordering.** Residues carry no information about the size of the originals. If chunk A's fold is 900000000 and chunk B's is 5, that says nothing about which underlying quantity was larger — reduction wraps. Any comparison, sort, or "is this bigger than the threshold" check must run on unreduced values. **Equality in the strong sense.** Equal residues mean the originals are *congruent*, not equal. Two distinct chunk streams can fold to the same checksum. That is fine for a checksum, whose job is cheap mismatch detection, but it means a match is evidence, not proof, and code must not treat it as identity. ## In review The patterns worth flagging: any `/` applied to values that are already reduced; averaging a modular accumulator by dividing by the count; comparing or sorting reduced values; and folding in an incoming value that was never reduced itself, so the multiply can exceed the width bound. The first two are the same bug wearing different clothes, and both produce plausible-looking numbers that are simply wrong.
- When does a modular inverse exist, and how does that steer the choice of modulus?An inverse of `b` exists exactly when `b` shares no factor greater than 1 with the modulus. A composite modulus therefore leaves some residues uninvertible — 2 has no inverse mod 4. Choosing a prime modulus makes every nonzero residue invertible, so division-by-inverse always works. Zero is still the exception at any modulus.
- Does subtraction behave exactly like addition here?As a congruence, yes — replacing operands by residues is safe. The difference is representational: the machine result can be negative when the left operand is smaller, and reducing a value already below the modulus does not fix that. Add the modulus back after any subtraction whose result feeds an index or a further multiply.
- Two chunk streams fold to the same checksum. What does that establish?That the underlying values are congruent modulo the chosen modulus — not that they are equal. Distinct streams can collide by construction, since reduction maps an unbounded range onto a finite one. Treat a match as strong evidence for cheap mismatch detection, never as proof of identity, and never infer ordering from residue comparison.
saying these in an interview costs you the question
- Claims (a / b) mod m equals (a mod m) / (b mod m)
- Treats truncating division as a modular operation
- Assumes a prime modulus makes plain division valid
- Assumes every residue has a modular inverse
- Compares or sorts reduced values as if ordering survived
- Reduces the accumulator but not the incoming value