skip to content

Operators and Evaluation Order

Integer division truncates toward zero, % takes the sign of the dividend, and dividing an integer by zero panics. Interviewers use a three-line snippet here to see whether you predict or guess.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

4

In Go, what do the expressions -7/2 and -7%2 evaluate to, and what rule fixes the sign of the remainder?

level: juniorimportance: must knowfreq 58%

answer

  1. two rules, one identity
  2. truncate, do not floor
  3. the remainder follows the dividend
  4. a negative index panics on lookup

basics

~10 s

Go truncates integer division toward zero, so -7/2 is -3, and the remainder takes the sign of the dividend, so -7%2 is -1. The identity x == (x/y)*y + x%y always holds.

solid answer

~40 s

Integer division in Go truncates toward zero rather than toward negative infinity, so `-7/2` is `-3`, not `-4`. The remainder is then defined by the identity `x == (x/y)*y + x%y`, which forces `%` to carry the sign of the **dividend**: `-7%2` is `-1`, and `7%-2` is `+1`. The spec also guarantees `|x%y| < |y|`. That is the opposite of Python's floored `%`, and it is the reason a sharding expression such as `int(hash) % numShards` can return a negative index and panic on the slice lookup when the converted hash happens to be negative. The fix is to do the modulo in unsigned arithmetic — `int(h % uint64(numShards))` — or to normalise with `((x % n) + n) % n`. Go's standard library has no integer floored-modulo helper, so you write that yourself.

code

go · 5 lines
go
x, y := -7, 2
fmt.Println(x/y, x%y) // -3 -1

p, q := 7, -2
fmt.Println(p/q, p%q) // -3 1

go deeper

for a junior

Be ready to state both rules from memory and evaluate the four sign combinations on the spot: -7/2 is -3 and -7%2 is -1. Recite the identity x == (x/y)*y + x%y as the reason.

for a middle

Explain why the remainder's sign follows from the truncating quotient rather than being a separate rule, and contrast Go's truncated division with the floored convention other languages use.

for a senior

Show that you spot the failure in review: a signed value feeding % into an index expression. Name the unsigned fix and explain why the resulting bug is data-dependent and therefore intermittent.

for a principal

Own the guidance: index-producing arithmetic should be unsigned by construction, not corrected afterwards, and a helper that a whole codebase uses beats twenty hand-written normalisations.

## The two rules Go's specification defines integer `/` and `%` together, not separately: - The quotient `q = x / y` is **truncated toward zero** — the fractional part is discarded, so the result moves toward 0 regardless of sign. - The remainder `r = x % y` is whatever makes `x == q*y + r` true, with `|r| < |y|`. Because the remainder is derived from a quotient that truncates toward zero, `%` always ends up with the **sign of the dividend** (the left operand), or is zero. The divisor's sign never shows up in the remainder. | expression | value | why | |---|---|---| | `7 / 2` | `3` | 3.5 truncated toward zero | | `-7 / 2` | `-3` | -3.5 truncated toward zero, not floored to -4 | | `7 / -2` | `-3` | same magnitude, negative sign | | `-7 % 2` | `-1` | -7 = (-3)*2 + (-1) | | `7 % -2` | `1` | 7 = (-3)*(-2) + 1 | The useful mental shortcut: **`/` cuts toward zero; `%` copies the dividend's sign.** ## Truncated versus floored The other common convention, used by Python and by mathematical modular arithmetic, is *floored* division: the quotient rounds toward negative infinity and the remainder takes the **divisor's** sign, so `-7 // 2` is `-4` and `-7 % 2` is `1`. Go, like C and Java, chose truncation, mainly because it maps directly onto what CPUs implement. Neither convention is more correct; the bug comes from assuming the one you learned elsewhere. One consequence worth knowing: `x % y` in Go is *not* a mathematical "mod n" that maps every input into `[0, n)`. It maps into `(-|y|, |y|)`. ## Where this actually bites: a shard index The classic production failure is a router that maps a key onto one of N shards: ```go h := fnv.New64a() h.Write([]byte(key)) shard := int(h.Sum64()) % len(shards) // BUG return shards[shard] ``` `h.Sum64()` returns a `uint64`. Converting it to `int` on a 64-bit platform reinterprets the top bit as a sign bit, so roughly half of all keys produce a **negative** `int`. Feed a negative dividend to `%` and you get a negative remainder, and `shards[-3]` panics with an index-out-of-range runtime error. What makes this miserable is that it is data-dependent: it depends on which keys the test happens to generate, so it shows up as a build that fails maybe one run in five, and the only evidence is a panic's stack trace pointing at the slice index rather than at the `%`. Three correct fixes: 1. **Stay unsigned.** `shard := int(h.Sum64() % uint64(len(shards)))`. An unsigned remainder can never be negative, and the result is smaller than `len(shards)`, so the conversion to `int` is safe. 2. **Normalise.** `shard := ((x % n) + n) % n` turns a truncated remainder into a floored one for positive `n`. 3. **Mask, when N is a power of two.** `shard := int(h.Sum64() & uint64(n-1))` avoids the division entirely. Option 1 is the one to reach for: it removes the signed value from the expression instead of repairing it afterwards. ## Details that come up in follow-ups - **Untyped constants follow the same rule.** `-7/2` written with literals is folded at compile time, still truncating toward zero, still `-3`. - **`%` is integers only.** Go does not define the `%` operator on floating-point operands at all; attempting it is a compile-time error. - **The one arithmetic special case.** If `x` is the most negative value of its signed type, `x / -1` equals `x` and `x % -1` is `0`, because the mathematically correct quotient has no representation in the type. - **Both operands must have the same type.** Go has no implicit numeric conversion, so `someInt % someInt64` does not compile; you convert explicitly, and it is exactly that explicit `int(...)` conversion that introduces the sign bug above. ## How to talk about it in an interview State the two rules (truncate toward zero, remainder follows the dividend), give `-7/2 == -3` and `-7%2 == -1` as evidence, then name a place where the sign matters — index computation, bucketing, round-robin, wrapping a clock value. The people who get caught by this are not the ones who cannot recite the rule; they are the ones who never considered that the dividend could be negative.

  • How would you map a possibly negative int onto the range 0 to n-1?
    Either normalise the truncated remainder with `((x % n) + n) % n`, or avoid the signed value entirely by converting to an unsigned type first and taking `x % uint64(n)`. When `n` is a power of two, `x & uint64(n-1)` does the same job without a division. The unsigned route is preferable because it removes the sign rather than patching it after the fact.
  • Why does converting a uint64 hash to int sometimes produce a negative number?
    On a 64-bit platform `int` is 64 bits and signed, so the conversion keeps all 64 bits and reinterprets the top one as the sign bit. Any hash with the high bit set becomes negative. The conversion never fails or reports anything — it is a pure reinterpretation — which is why the bug surfaces far away, at the slice index.
  • Does Go's % operator work on float64?
    No. Go defines `%` only for integer operands; writing it with floating-point operands is a compile-time error rather than a run-time surprise. That is a deliberate difference from languages that overload `%` for both, and it forces you to reach for a floating-point-specific helper when you really want one.

Truncation toward zero is like deleting the digits after the decimal point rather than rounding down: -3.5 becomes -3, not -4. The remainder is then whatever is left over on the same side of zero you started from.

saying these in an interview costs you the question

  • Says Go floors like Python, so -7/2 is -4
  • Claims an integer remainder is always non-negative
  • Thinks % takes the sign of the divisor
  • Uses % on a converted signed hash for a shard index without guarding the sign
  • Believes % also works on float64 operands
open as a page

In Go, what happens when an integer is divided by zero, at compile time and at run time?

level: middleimportance: should knowfreq 42%

basics

~20 s

A constant zero divisor is rejected by the compiler. A divisor that is zero only at run time causes a panic carrying a runtime.Error with the message 'integer divide by zero', which a deferred recover in the same goroutine can catch.

open as a page

Which evaluation orders does the Go spec guarantee inside one expression, and which are left unspecified?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Go orders function calls, method calls, channel receives and the operands of && and || lexically left to right. The order of everything else, such as plain variable reads next to a call, is unspecified. Assignment evaluates all right-hand expressions and left-hand index operands first, then assigns left to right.

open as a page

In Go, what does a shift count larger than the operand's width produce, and what does a negative count do?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

Go does not mask shift counts. Shifting a uint64 left by 64 or more yields 0, and shifting a negative signed value right far enough yields -1. A shift count that is negative at run time panics.

open as a page