Why can a remainder like (dayIndex + delta) % 7 come out negative, and how do you normalize it?
answer
- who decides a remainder's sign?
- the divisor's sign is not what matters
- a negative offset lands left of zero
- shift by one full modulus
- then reduce a second time
basics
~20 sIn truncated-division languages the remainder carries the sign of the dividend, so a day offset that lands left of zero yields a negative result. Normalize with ((x % 7) + 7) % 7 to map any integer into 0..6.
solid answer
~50 sThere are two conventions for integer division, and the remainder's sign follows whichever one the language picked. Under truncated division — the common choice — the quotient rounds toward zero and the remainder takes the **dividend's** sign, so `-3 % 7` is `-3`, not `4`. Under floored division the quotient rounds toward negative infinity and the remainder takes the **divisor's** sign, giving `4`. Neither is wrong; they differ on negative inputs only. If your code indexes a weekday table, a bucket array, or any 0..m-1 range, a negative remainder is an out-of-range index waiting to happen. The portable fix is `((x % m) + m) % m`: the first reduction bounds the magnitude below `m`, adding `m` lifts any negative into range, and the second reduction removes the extra `m` when the value was already non-negative. A branch (`if r < 0: r += m`) is equivalent and cheaper.
go deeper
Be ready to say out loud that a remainder can be negative and to write the normalization on the spot. Know that indexing a fixed-size table with an unnormalized remainder is the bug this prevents.
Explain the mechanism, not just the fix: truncated division rounds toward zero and pins the remainder to the dividend's sign, floored division rounds down and pins it to the divisor's. Derive both from the division identity.
Show you catch this in review, especially the subtraction form where both operands are already reduced and nothing looks negative. Point out that tests over non-negative inputs will never surface it.
Own the convention: a shared helper for range normalization, applied at every index computation, is cheaper than auditing each call site — particularly in a codebase whose pseudocode and specs were written against a different division convention than the implementation language.
## The setup A scheduling routine stores dates as an integer day index and answers "which weekday is this?" by reducing the index modulo 7. Shifting a date backwards produces a negative offset — a reminder set three days before a stored index, a timezone rollback, a range that starts before the epoch you chose. The natural line `weekday = (dayIndex + delta) % 7` then hands you `-3`, and `weekdayNames[-3]` is either a crash, a silent wrap to the wrong end of the table, or garbage, depending on the platform. ## Two conventions, both defensible Integer division has to decide what to do with a fractional quotient, and the remainder is then pinned by the identity that every implementation honours: ``` a == (a / b) * b + (a % b) ``` Given that identity, fixing the rounding rule fixes the remainder's sign. - **Truncated division** rounds the quotient toward zero. `-17 / 5` is `-3`, so the remainder must be `-17 - (-15) = -2`. The remainder takes the **dividend's** sign, and its magnitude is always less than the divisor's. - **Floored division** rounds the quotient toward negative infinity. `-17 / 5` is `-4`, so the remainder is `-17 - (-20) = 3`. The remainder takes the **divisor's** sign, so with a positive modulus it is always in `0..m-1`. Mainstream runtimes genuinely split here, and that split is the whole reason this is an interview question: C, Java and Go truncate toward zero, so their remainder can be negative; Python and Ruby floor, so a positive modulus always yields a non-negative result. Both camps satisfy the same identity — they just picked different rounding. Code and pseudocode ported between the two camps carries a latent bug for every negative input, and a test suite that only ever exercises non-negative offsets will never catch it. Note also that this is about the *sign*, not the *magnitude*: every convention guarantees `|a % m| < |m|`. That guarantee is what makes the fix cheap. ## The normalization, and why it is written that way ``` r = ((x mod m) + m) mod m // for m > 0 ``` Step by step: 1. `x mod m` bounds the magnitude: the result lies in `-(m-1) .. m-1`, whatever `x` was. This is the step people skip. 2. `+ m` lifts the whole range to `1 .. 2m-1`, which is non-negative. 3. The final `mod m` drops the extra `m` back off when the value was already non-negative, landing everything in `0 .. m-1`. A very common half-fix is `(x + m) % m`. It works *only* when `x` is already known to be greater than `-m`; a raw offset of `-30` with `m = 7` is still negative after adding 7. Another half-fix is `abs(x % m)`, which is simply a different number: with `x = -2, m = 5` it gives `2`, while the true residue is `3`. Absolute value mirrors the residue around zero instead of shifting it. If the extra division bothers you, the branch form is exactly equivalent and usually faster, because a division is far more expensive than a predictable branch: ``` r = x mod m if r < 0 r = r + m ``` One addition suffices here precisely because step 1 already bounded the magnitude below `m`. ## What it does to real code The damage is rarely a visible crash. Weekday tables, ring indices, bucket selection and colour-cycling all reduce an integer into a small range, and a negative index either throws at the boundary or — where negative indexing is legal — silently reads from the far end. Either way, the failure only shows up for inputs that dip below zero, which are usually the rarer inputs: dates before an anchor, deltas from a subtraction, counters that were decremented past their start. The reviewer's version of this bug is subtraction. Two values already reduced into `0..m-1` look safe, but `(a - b) mod m` is negative whenever `a < b`, and the reduction does nothing to rescue it because the operand was already smaller than the modulus. Any place your code subtracts residues and then indexes with the result needs the same normalization as any place it adds a signed delta. ## What the interviewer is checking That you know remainder sign is a *convention*, not a law; that you can state which sign follows which operand under truncation; that your normalization handles arbitrarily negative input rather than just `-1`; and that you spot the subtraction case, where nothing in the expression looks negative at a glance.
- A diff computes (a - b) % m on two values already reduced into 0..m-1 and uses the result as a bucket index. What do you flag?That it is negative whenever `a < b`. Both operands are already below the modulus, so the reduction changes nothing and the difference stays as-is — a negative index. The subtraction needs the same normalization as any signed offset: add the modulus once, or reduce and add if the difference could be smaller than `-m`. This is the version reviewers miss, because nothing in the expression looks negative.
- If the offset can be many multiples of the modulus below zero, does adding the modulus once still suffice?Yes, as long as you reduce first. `x mod m` already has magnitude strictly less than `m` under every convention, so the worst case afterwards is `-(m-1)`, and a single addition lifts that into range. What fails is adding before reducing: `(x + m) mod m` with `x = -30` and `m = 7` is still negative.
- Why prefer a branch over the double-remainder form?Integer division is one of the more expensive arithmetic instructions, and the double-remainder form pays for two of them; the branch form pays for one plus a comparison that predicts almost perfectly in a loop where inputs are usually non-negative. The double-remainder form wins on branch-free code paths and on readability, so pick by context rather than dogma — they compute the same value.
A clock face only shows 12 positions, but nothing stops you from counting backwards past 12 o'clock. Normalizing is walking one full lap forward before you read the dial.
saying these in an interview costs you the question
- Claims a remainder is always non-negative
- Says the sign follows the divisor under truncated division
- Writes (x + m) % m for arbitrarily negative x
- Uses abs() to force a residue non-negative
- Uses an unnormalized remainder directly as an index
- Assumes every language agrees on remainder sign