JAVASCRIPT / OPERATORS
Arithmetic operators and the remainder trick
Use JavaScript's arithmetic operators with confidence, and use % to wrap counters, split totals into units, and test divisibility without sign surprises.
What you will learn
- Reach for Math.trunc or Math.floor when you need whole numbers, since / never truncates
- Predict a % b from the left operand's sign, not the divisor's
- Wrap any index or counter into 0..n-1 with ((i % n) + n) % n
- Split a total into units by pairing Math.floor(t / u) with t % u
Understanding Arithmetic operators and the remainder trick
JavaScript has six arithmetic operators: +, -, *, /, % and **. Because an ordinary number is a 64-bit binary float, / never truncates and there is no separate integer-division operator, so 7 / 2 is 3.5 and whole results come from Math.trunc (toward zero) or Math.floor (toward -Infinity). ** binds tighter than * and is right-associative, which is why 2 ** 3 ** 2 means 2 ** 9 and not 8 ** 2.
The % operator is a remainder, not a mathematical modulo. Read a % b as a - b * Math.trunc(a / b): peel off as many whole copies of b as fit while moving toward zero, and report what is left over. Truncating toward zero is exactly what makes the result carry the dividend's sign, so -7 % 3 is -1 and 7 % -3 is 1; the divisor's sign never reaches the answer. Languages that define the same operation with floored division give 2 for -7 % 3, and that mismatch is where most confusion comes from.
The useful consequence is that for a non-negative left operand, a % b always lands somewhere in 0..b-1, so % turns an unbounded counter into a bounded position. That single operator covers cycling through a list, striping rows, filling a grid, and divisibility tests, and paired with a division it splits a quantity into units: Math.floor(t / 60) minutes and t % 60 seconds. When the left side can go negative, recover the mathematical modulo with ((a % b) + b) % b, where the inner % shrinks the magnitude below b, the addition lifts it above zero, and the outer % cancels that addition when it was not needed.
const totalSeconds = 4267;
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
console.log(`${minutes}m ${seconds}s`);
console.log(7 / 2, Math.trunc(7 / 2)); // no integer division operator
console.log(-7 % 3, 7 % -3); // the sign comes from the left operand
console.log(5.5 % 2); // remainder is not integers-only
console.log(2 ** 3 ** 2); // ** is right-associative: 2 ** 9a % b is what remains after removing whole multiples of b, and it keeps the sign of the left operand, which is why it wraps values into a fixed range but needs a guard for negatives.
Worked examples
Cycling an index in both directions
Shows why a bare % breaks backwards steps through a list and how the double-% guard fixes it.
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
function dayAfter(index, shift) {
const raw = (index + shift) % days.length;
return days[(raw + days.length) % days.length];
}
console.log(dayAfter(5, 3)); // Sat, three days on
console.log(dayAfter(1, -4)); // Tue, four days back
console.log((1 - 4) % 7); // the value the guard repairsExample explained
Line 1(5 + 3) % 7 is 1, and for an already in-range value the + days.length and outer % 7 change nothing.
Line 2(1 - 4) is -3, and -3 % 7 is -3 because the remainder copies the dividend's sign, so days[-3] would be undefined.
Line 3Adding days.length turns -3 into 4 and the outer % 7 leaves it there, giving 'Fri'.
Line 4The last line prints the raw -3 so you can see the exact value the guard is correcting.
Peeling digits and testing parity
Uses % 10 with a floored division to walk a number's digits, and shows which odd-number test survives negatives.
function digitSum(n) {
let rest = Math.abs(n);
let sum = 0;
while (rest > 0) {
sum += rest % 10;
rest = Math.floor(rest / 10);
}
return sum;
}
console.log(digitSum(9075));
console.log(-3 % 2 === 1, -3 % 2 !== 0);Example explained
Line 1rest % 10 gives the last digit, and Math.floor(rest / 10) drops it because / keeps the fraction that floor throws away.
Line 2The loop stops when rest hits 0, so digitSum(9075) accumulates 5 + 7 + 0 + 9 = 21.
Line 3-3 % 2 is -1, so the popular `n % 2 === 1` test reports false for every negative odd number.
Line 4`n % 2 !== 0` does not care about the sign, so it stays correct on both sides of zero.
Edges: unary minus, zero divisors, coercion, floats
Four arithmetic behaviours that surprise people: exponent grouping, division by zero, non-number operands, and remainders of floats.
console.log((-2) ** 2, -(2 ** 2));
console.log(10 % 0, 10 / 0, 0 / 0);
console.log('6' * '7', '6' + '7');
console.log(0.1 + 0.2, (0.1 + 0.2) % 0.1);Example explained
Line 1The parentheses are mandatory: `-2 ** 2` is a SyntaxError, and the two legal readings really do differ, 4 versus -4.
Line 2A zero divisor fails quietly and differently per operator: % gives NaN, / gives Infinity, and 0 / 0 gives NaN.
Line 3'6' * '7' is 42 because every arithmetic operator except + converts its operands to numbers; + prefers concatenation, so '6' + '7' is the string 67.
Line 40.1 + 0.2 is a hair above 0.3 in binary floating point, so its remainder by 0.1 is a tiny leftover instead of 0 — never test float divisibility with % against 0.
Important notes
x % 0 is NaN rather than a thrown error, and NaN then spreads through every later calculation, so validate the divisor or check the result with Number.isNaN.
Parity checks stop working past 2 ** 53, where consecutive integers are no longer all representable: (2 ** 53 + 1) % 2 is 0 even though the intended value is odd. Use BigInt for integers that large.
Common mistakes
Expecting -1 % 12 to be 11 when wrapping a clock hand or an array index: it is -1, and arr[-1] is undefined rather than an error, so the bug surfaces later as blank output.
Writing n % 2 === 1 as the odd test, which reports false for -3, -5 and -7 and silently misclassifies half the number line.
Carrying integer-division habits from C or Java into JavaScript: 7 / 2 is 3.5, so arr[7 / 2] is undefined and counters drift into fractions instead of stepping cleanly.
Try it yourself
Change, predict, then run
In a browser console, write formatDuration(s) that turns 4267 into "71:07" using Math.floor(s / 60), s % 60 and padStart(2, '0'), then confirm it returns "0:00" for 0 and "1:00" for 60.
Open the JavaScript workspaceCheck your understanding
You cycle a 5-item playlist with list[(index + step) % 5]. It works for forward steps but returns undefined when the user skips backwards. What is going wrong?
- % returns a floating point value, so the index is no longer a whole number
- Array indexes wrap automatically for positive numbers only, and % is irrelevant here
- % keeps the sign of the left operand, so a negative sum produces a negative index
- % needs a power-of-two divisor to wrap a range correctly
Show answer
With index 2 and step -4, (2 - 4) % 5 is -2, because % truncates the division toward zero and copies the dividend's sign; list[-2] is undefined since arrays have no negative index. The floating point option is tempting because / really does produce fractions, but a remainder of two integers is an integer, so the type is fine and only the sign is wrong. Writing ((index + step) % 5 + 5) % 5 fixes it.