Operators, Expressions and Precedence

Getting Started with Code · 30 min

Data structures & algorithms · Foundations

Operators, expressions & precedence

Code is not read left to right. This page collapses one mixed expression down to a single value, one operator at a time, so you can see exactly where your reading and the compiler's part company.

Collapse an expression yourself
10 + 4729 % 10 * 2 - 110 + 9 * 2 - 110 + 18 - 127. Read it left to right like a sentence and you get 17.

01 The idea

Every expression is a collapse, not a sentence

You will write more expressions than anything else. An expression is any piece of code that produces a value: 4729, n % 10, i < n && a[i] != 0. The compiler does not scan one left to right the way your eye does. It first decides which operator owns which operands, using a fixed ladder of rungs, and only then computes anything.

Get that grouping wrong and nothing crashes. The code compiles, runs, and quietly returns a number that is close enough to look right. a + b / 2 is a bug you can only find by reading, which is exactly why interviewers keep asking about it.

An expression is not read left to right. It is collapsed. Find the operator on the highest rung, replace it and its two operands with one value, and repeat until one value is left. Brackets are the only thing that can move an operator up the ladder.
ExpressionAnything that produces a value. 4729 is one, n % 10 is one, and so is a > b. A statement does something; an expression is something.
PrecedenceWhich operator grabs its operands first when two different operators sit side by side. * is a rung above +, so 1 + 2 * 3 means 1 + (2 * 3), which is 7.
AssociativityThe tie-break when two operators share a rung. Nearly all of them run left to right, so 100 / 7 * 7 means (100 / 7) * 7 — 98 in whole-number arithmetic, not 100.

02 Hand trace

Taking 4729 apart with two operators

Before precedence, meet the workhorse. Take 4729. Two operators are enough to strip it digit by digit: % reads the last digit, and / throws that digit away. Every row below is one turn of the loop. The rose box is the digit % 10 is about to hand you; the green row is where the loop stops.

n = 472947294729 % 10 → 9  take it  ·  4729 / 10 → 472  drop it
n = 472472472 % 10 → 2  ·  472 / 10 → 47
n = 474747 % 10 → 7  ·  47 / 10 → 4
n = 444 % 10 → 4  ·  4 / 10 → 0  the last one goes
n = 0stopn > 0 is false — digits came out 9, 2, 7, 4

Read the two halves of each note separately, because they do different jobs. % 10 never changes n; it hands you a copy of the last digit and leaves the number alone. / 10 is the one that shrinks n, and it only works because whole-number division throws the remainder away — 4729 / 10 is 472, not 472.9. That is what / does on two whole numbers in C, C++ and Java; Python spells the same operation //, because a plain / there would hand you 472.9 and the trick would fall apart. Run the pair in a loop and the digits arrive backwards: 9, 2, 7, 4. That is why the same four digits that add up to 22 also spell 9274, and it is why % 10 and / 10 show up in digit sums, palindrome checks and reversals alike.

03 Code

The same two digits, one line apart

Now write it down and watch precedence break it. Both blocks below try to average the last two digits of 4729, which are 9 and 2. Every variable here is a whole number — C, C++ and Java spell that int — so / truncates, exactly as it did in the trace. The left block reads more naturally out loud. It is also wrong, and it never says so.

Reads well · computes the wrong thing

// average of 4729's last two digitsint n   = 4729;int avg = n % 10 + n / 10 % 10 / 2;// collapses to 9 + (2 / 2) -> 9 + 1// avg = 10, and 10 is not between 9 and 2

Named parts · says what it means

int n    = 4729;int last = n % 10;          // 9int next = n / 10 % 10;     // 2int avg  = (last + next) / 2;// (9 + 2) / 2 -> 11 / 2 -> avg = 5

/ sits a rung above +. In the left version the / 2 grabs only the operand touching it, which is the tens digit, not the sum. The line means 9 + (2 / 2), so it halves one digit and adds the other whole. Brackets are the only way to pull a + up above a /.

n / 10 % 10 needs no brackets, and that is not luck. / and % share a rung, and that rung runs left to right, so it divides first and takes the remainder second — exactly the two moves from the trace. Bracket it the other way and you get n / (10 % 10), which is division by zero.

Two names beat one clever line. Once last and next exist, the average reads like an average and the precedence question stops being a question. The two blocks do not agree — the left one answers 10 and the right one answers 5 — but the named version and the one-line (n % 10 + n / 10 % 10) / 2 do, and any optimising compiler turns them into the same machine work. The clarity is free.

Two operators that also write. i++ hands back the old value and then adds one, so after i = 5 the line a = i++ leaves a at 5 and i at 6. ++i adds first and hands back 6, leaving both at 6. Compound assignment has the same double life: x *= a + b is x = x * (a + b), never x = x * a + b, because assignment is the loosest rung and takes the entire right-hand side.

05 Cheat sheet

The ladder, tightest rung at the top

RungOperatorsWhat catches people out
1 · brackets( )The only override there is. Free to add and never wrong.
2 · unary++ -- ! -xBinds to one operand, tighter than any arithmetic. !a == b is (!a) == b.
3 · multiplicative* / %Left to right, so 100 / 7 * 7 is 98. Whole-number / truncates before * ever runs.
4 · additive+ -A rung below /, so a + b / 2 halves b alone and is not the average.
5 · relational< <= > >=Below all arithmetic, so i < n - 1 needs no brackets.
6 · equality== !=Below the size comparisons, so a < b == c compares a true/false against c.
7 · logical AND&&Left side first. If it is false the right side never runs at all.
8 · logical OR||A rung below &&, so a || b && c is a || (b && c).
9 · ternary? :Below every logical operator, so the whole condition is settled before ? is reached.
10 · assignment= += -= *= /= %=Loosest of all, and it runs right to left: a = b = 0 means a = (b = 0). The full right-hand side is computed first.
Same rung? Go left to right.Every two-operand rung above is left-associative except assignment and the ternary, which go right to left. 100 / 7 * 7 is (100 / 7) * 7 = 98; 100 / (7 * 7) would be 2.
i++ gives the old value, ++i the new one.Starting from i = 5: a = i++ leaves a = 5, i = 6. a = ++i leaves both at 6. Either way i ends up 6 — only the handed-back value differs.
The ternary is an expression.max = a > b ? a : b is one assignment, not a hidden if-statement. Because it produces a value it can sit inside a larger expression, and because it sits below || the line a || b ? x : y means (a || b) ? x : y.

06 Where & why

When to trust the ladder and when to type brackets

Nobody gives you marks for writing an expression with no brackets in it. Precedence exists so that ordinary arithmetic reads like ordinary arithmetic. Past that point brackets cost you two keystrokes and save the next reader a trip to a reference table, so the real skill is knowing the handful of places where the ladder is genuinely obvious.

Lean on precedence when…

It is plain arithmetica * b + c groups the way school algebra groups. Bracketing it adds noise and makes a reader wonder what the trick is.
It is the digit idiomn % 10 and n / 10 % 10 appear in so much code that every reader parses them instantly. They also happen to be same-rung, left to right.
One comparison against one arithmetic resulti < n - 1 has exactly one legal reading, because - sits a rung above < in every C-family language and nothing can drag it back down.
The compiler would warn you otherwisegcc and clang warn on a && b || c and on bitwise mixed with equality, precisely because those are the two places people guess wrong.

Use brackets or names instead when…

SituationBetter choiceWhy
You mixed && and || in one conditionWrite (a && b) || c&& is a rung above ||, so the unbracketed line is not what most people read. The compiler warns for the same reason.
You are averaging or scaling a sumWrite (a + b) / 2/ is a rung above +, so a + b / 2 halves b alone — the exact bug in section 03.
One line mixes arithmetic, a comparison and a logical operatorTwo named variablesThree rungs in one line is where review comments come from. A name explains the intent; a bracket only fixes the grouping.
You are counting on which operand runs firstSeparate statementsPrecedence decides grouping, not order of evaluation. In C, f() + g() may call g first, and i = i++ + 1 is undefined behaviour.
Precedence tells you how an expression groups. It does not tell you the order in which the operands are computed, and it does not make a line readable. Memorise four rungs — * / %, then + -, then the comparisons, then && before || — and bracket everything else.

07 Interview questions

Say these out loud

Question three gets asked on a whiteboard with no compiler in the room. Practise talking through the collapse rather than announcing the answer.

What is operator precedence?
Precedence is the fixed ranking that decides which operator grabs its operands first when two different operators sit side by side. * ranks above +, so 1 + 2 * 3 groups as 1 + (2 * 3) and gives 7, not 9. It settles grouping only — it says nothing about which operand is computed first.
What is associativity, and when does it actually matter?
Associativity is the tie-break when two operators share a rung. Nearly everything in the C family is left to right, so 100 / 7 * 7 is (100 / 7) * 7 = 98 with whole numbers, not 100. It only matters when regrouping would change the answer, which is why -, / and % are where it bites — (9 - 4) - 2 is 3 and 9 - (4 - 2) is 7. Assignment and the ternary are the common right-to-left exceptions.
Evaluate 10 + 4729 % 10 * 2 - 1 and talk me through it.
27. The multiplicative rung goes first, and it runs left to right, so 4729 % 10 is 9, leaving 10 + 9 * 2 - 1. Still on that rung, 9 * 2 is 18. Now the additive rung, left to right: 10 + 18 is 28, then 28 - 1 is 27. Reading it left to right like a sentence would give 17, and that is the trap.
Why does n % 10 give the last digit?
Because in base ten every digit position above the units is a multiple of 10, so dividing by 10 leaves exactly the units digit as the remainder. 4729 = 472 × 10 + 9, so the remainder is 9. The companion move is n / 10, which throws that digit away using whole-number division. Watch negatives: in C99 and Java -4729 % 10 is −9, while Python gives 1.
What does 100 / 7 * 7 give in C or Java, and why is it not 100?
98. / and * share a rung and run left to right, so the division happens first, and on two ints it truncates: 100 / 7 is 14, then 14 * 7 is 98. The two lost units never come back. Bracketing it as 100 / (7 * 7) gives 2 instead, which is a good check that you understand the grouping rather than the numbers.
What is short-circuit evaluation?
With && the left side is evaluated first, and if it is false the right side is never evaluated at all, because the answer cannot change. || is the mirror image: a true left side ends it. This is guaranteed by the language specification in C, C++, Java, Python and JavaScript, so you can rely on it, and it means the right operand may contain work that simply never happens.
Give me a case where short-circuiting is a correctness requirement, not an optimisation.
A guard before an unsafe access. if (i < n && a[i] == x) is safe precisely because a[i] is never touched when i < n is false; swap the two halves and you read out of bounds. The same shape appears as if (p != null && p.value == 3) against a null pointer. Both are wrong in a language without short-circuiting.
What is the difference between i++ and ++i?
Both add one to i; they differ in the value the expression hands back. Starting from i = 5, a = i++ leaves a at 5 and i at 6, while a = ++i leaves both at 6. On its own line as a statement they are identical, which is why for loops work either way.
What does x *= a + b expand to?
x = x * (a + b). Assignment is the loosest rung on the ladder, so the whole right-hand side is collapsed to one value before the multiply-and-store happens. People expect x = x * a + b, which is a different number unless b happens to be 0 or x happens to be 1. The same rule covers +=, -=, /= and %=.
Is a &lt; b &lt; c valid C, and what does it do?
It compiles and it does not mean what it reads. < is left-associative, so it is (a < b) < c: the first comparison produces 0 or 1, and that is then compared with c. With a = 1, b = 2, c = 3 you get (1 < 2) = 1, then 1 < 3 = true, which is right by accident. Python is the exception — it chains comparisons properly and means what you wrote.
&amp; versus &amp;&amp; — what is the difference?
&& is logical, works on true and false, and short-circuits. & is bitwise, works on every bit of both operands, and always evaluates both sides. The precedence trap is worse than the semantics: == binds tighter than &, so x & 1 == 0 is x & (1 == 0), which is x & 0 and therefore always 0. You want (x & 1) == 0.
When does precedence actually bite you in real code, and what do you do about it?
Honestly, rarely in plain arithmetic — everyone reads a * b + c the same way. It bites in three places: a sum being divided, && mixed with ||, and bitwise operators next to a comparison. The fix is not memorising all fifteen C rungs; it is bracketing those three shapes, pulling long conditions into named variables, and leaving compiler warnings switched on, because gcc and clang already flag the exact cases people get wrong.

08 Practice problems

Bracket it before you run it

Work each one out on paper first and write down the value you expect. The ones that are a single expression you can then type straight into the console above; the rest you check by reading the ladder. The gap between what you expected and what comes back is the part worth studying.

Pull out the hundreds digit

Easy
Write one expression using only % and / on whole numbers that returns the hundreds digit of any non-negative n, and state what it gives for 4729.
Follow-up
n % 100 / 10 also compiles and also hands back a single digit — just the wrong one. Say which digit it returns for 4729 and why.
Show the hint
Decide whether you throw away the digits below the one you want first, or the ones above it; one operator cannot do both.

Bracket the average

Easy
Given avg = a + b + c / 3 on whole numbers with a = 9, b = 2, c = 8, state the value it computes and rewrite the line so it is the mean.
Follow-up
Even after you bracket it correctly the answer still is not the true mean of 9, 2 and 8 — work out what the fixed line gives and say what happened to the missing fraction.
Show the hint
Ask which single operand the / 3 is attached to before you add a single bracket.

Digits and their count

Medium
Write a loop over a non-negative n using only % and / that returns both the sum of its digits and how many digits it has; report both for 4729.
Follow-up
n = 0 is a legal input with one digit, and the obvious while (n > 0) loop reports zero digits for it.
Show the hint
A while loop that tests before the first pass never runs for 0 — either test at the bottom or handle 0 before the loop.

The guard that has to come first

Medium
You have two whole numbers total and count, and count may be 0. Write the single if condition that tests whether the average total / count equals x without ever crashing, then say exactly what goes wrong if the two tests are written the other way round.
Follow-up
Written the wrong way round the line still compiles and still behaves for every non-zero count, so no test you are likely to write will catch it — it only breaks on the one input the guard exists for.
Show the hint
&& evaluates its left side first and stops there the moment it is false, so decide which of the two tests must be the one that can never crash.

Cycling round four seats

Medium
Seats in a ring are numbered 0 to 3. Write the expression that gives the seat after seat s, and evaluate it for s = 3.
Follow-up
s + 1 % 4 compiles, and it is wrong for exactly one value of s in range — find that value and explain the rung that causes it.
Show the hint
% is a rung above +, so work out what 1 % 4 is on its own first.

Count what never ran

Hard
Take hit = x % 3 == 0 || x % 5 == 0 && x % 2 == 0. For x = 9, x = 10 and x = 14, state how many of the three remainders are actually computed and what hit is. Then bracket the line so it means “divisible by 3 or 5, and even”.
Follow-up
The three inputs are chosen so that a different number of remainders runs in each, and one of the three answers flips once you add the brackets most readers assume are already there.
Show the hint
&& is a rung above ||, so write in the brackets the language already applies, then evaluate the top level strictly left to right.