Which evaluation orders does the Go spec guarantee inside one expression, and which are left unspecified?
answer
- some things are ordered, most are not
- calls and receives run left to right
- assignment has two phases
- left-hand index operands evaluate before assigning
basics
~20 sGo 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.
solid answer
~50 sGo guarantees far less than most languages. Within an expression, statement or return, the spec orders only **function calls, method calls, channel receive operations and binary logical operations** — those happen in lexical left-to-right order, which is also why `&&` and `||` reliably short-circuit. The relative order of *other* operands is explicitly unspecified: in `[]int{a, f()}` where `f` mutates `a`, the slice may be `[1 2]` or `[2 2]` and both are conforming. Assignment has its own two-phase rule: first every right-hand expression and every index or pointer-indirection operand on the left is evaluated, then the assignments are carried out left to right. That is what makes `a, b = b, a` a real swap with no temporary, and what makes `i, x[i] = 1, 2` set `x[0]`, not `x[1]`, when `i` starts at 0.
code
go · 6 linesx := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2 // i == 1, and x[0] == 2 because the index used the old i
a, b := 1, 2
a, b = b, a // a real swap; no temporary neededgo deeper
Know that a, b = b, a swaps two variables in one statement and needs no temporary, and that && stops as soon as the left side settles the result.
List the constructs the spec orders — calls, method calls, channel receives, && and || — and explain the two-phase assignment rule well enough to predict what i, x[i] = 1, 2 does.
Recognise the smell in review: an operand with a side effect sitting beside another operand in the same expression. Connect it to an intermittent CI failure and give the mechanical fix of splitting the statement.
Own the standard: prefer statements whose order is visible over clever one-liners, and be able to say why a language leaving operand order open is a reasonable trade rather than a defect to work around.
## Precedence is not evaluation order Precedence decides how an expression *parses* — which operand belongs to which operator. Evaluation order decides *when each subexpression actually runs*. They are independent, and Go pins down much less of the second than people assume. ## What Go actually guarantees Within a function, when evaluating the operands of an expression, assignment or return statement, the spec orders exactly four kinds of thing, in lexical left-to-right order: - function calls - method calls - receive operations on a channel - binary logical operations, that is `&&` and `||` That last item is the short-circuit guarantee. `a() && b()` always evaluates `a()` first and only calls `b()` if `a()` returned true. Go has no non-short-circuiting boolean operator at all — `&` and `|` are defined on integers, not on `bool` — so there is no way to accidentally force both sides, and no need for the "use `&` when you want both evaluated" habit some languages carry. At package level the rule is different: initialisation dependencies decide the order of variable initialisation expressions, and only expressions with no dependency relation fall back on declaration order. ## What Go deliberately leaves unspecified Everything else. The relative order of a call and a plain operand next to it is not defined: ```go a := 1 f := func() int { a++; return a } v := []int{a, f()} // may be [1 2] or [2 2]; both conform ``` The spec's own example goes further: in `y[f()], ok = g(z || h(), i()+x[j()], <-c), k()` the *calls and the receive* are ordered `f()`, `h()` (only if `z` is false), `i()`, `j()`, `<-c`, `g()`, `k()` — but when the indexing of `x`, the evaluation of `y`, and the read of `z` happen relative to those is not specified. This is a design choice, not an oversight. Fully ordering every operand would constrain the compiler's freedom to reorder loads for no user-visible benefit, and Go's answer is that any expression whose result depends on that ordering is a bug in the program, not a question for the language. ## The two-phase assignment rule Assignment is specified more tightly, and this is the part that shows up in interviews. It proceeds in two phases: 1. **Phase one:** the operands of index expressions and pointer indirections on the **left**, together with all the expressions on the **right**, are evaluated. 2. **Phase two:** the assignments are carried out left to right. Three consequences: ```go a, b = b, a // a real swap: both right-hand values are read in phase one x := []int{1, 2, 3} i := 0 i, x[i] = 1, 2 // i becomes 1, and x[0] becomes 2 - the index used the OLD i x[0], x[0] = 1, 2 // phase two runs left to right, so x[0] ends up 2 ``` The swap is the friendly face of the rule and the `i, x[i]` case is the sharp one. The index operand `i` on the left is evaluated in phase one, while `i` still holds 0, even though the assignment to `i` is listed first. ## Why this matters in production The failure mode is a flaky build. Code whose result depends on unspecified order is not *wrong* on any given run — it is wrong only sometimes, and "sometimes" is decided by the compiler version, the target architecture, the optimisation the inliner happened to make, or whether the race detector build shifted things around. A test that passes on your laptop and fails one CI run in twenty, in a package with no goroutines in it at all, is the shape this takes. Three habits keep you out of it: - **One side effect per statement.** If a function mutates something another operand reads, give it its own line and a name. The extra local costs nothing and makes the order explicit. - **Do not rely on `go vet` here.** Vet catches many things; "this expression depends on unspecified evaluation order" is not generally one of them. The compiler will not warn either, because the program is legal. - **Read assignments with the two-phase rule in mind** when an index or a pointer appears on the left and the same variable appears on the right. ## What to say when asked Name the four guaranteed-ordered constructs, say that everything else is unspecified, and then give the two concrete demonstrations: `[]int{a, f()}` for the unspecified part and `i, x[i] = 1, 2` for the two-phase assignment. Finish with the practical rule — if you had to think about the order, split the statement — which is the answer an interviewer is actually looking for.
- Why does a, b = b, a swap correctly without a temporary?Because assignment evaluates every right-hand expression in phase one, before any assignment is carried out. Both `b` and `a` are read while they still hold their original values, and only then are the two assignments performed left to right. A temporary would be redundant; the language already holds both values.
- Does Go guarantee that && evaluates its left operand first?Yes. Binary logical operations are in the list of constructs the spec orders lexically left to right, so `&&` and `||` always evaluate the left operand first and skip the right one when the result is already decided. Go also has no non-short-circuiting boolean operator — `&` and `|` are integer operators and do not apply to `bool` — so both-sides evaluation is not even expressible.
- You are triaging a test that fails on CI once in twenty runs with no concurrency in the package. How does evaluation order enter the picture?Look for a statement where one operand mutates state another operand reads — a call beside a plain variable in a composite literal, an argument list, or a return. That order is unspecified, so an inliner decision or a different toolchain can flip it while your local build stays stable. The fix is mechanical: hoist the call into its own statement with a named result.
- Is operator precedence enough to predict which operand runs first?No. Precedence only decides how the expression groups — which operand attaches to which operator. Evaluation order is a separate rule, and in Go it constrains only calls, method calls, channel receives and the logical operators. Two expressions can parse identically and still evaluate their non-call operands in either order.
saying these in an interview costs you the question
- Assumes every operand is evaluated strictly left to right
- Believes a, b = b, a needs a temporary variable
- Thinks the left-hand index in i, x[i] = 1, 2 uses the new i
- Says && may evaluate its right operand first
- Confuses operator precedence with evaluation order
- Expects go vet to flag order-dependent expressions