A team reorders the two operands of a short-circuiting AND to put the cheaper test first - when does that break?
answer
- cosmetic on the surface, load-bearing underneath
- does the second test presuppose the first
- effects have an evaluation frequency
- pure, total and independent, then swap
- expected cost weighted by the left test
basics
~20 sReordering is safe only when neither operand depends on the other, neither has an effect the program needs, and neither can fail or hang. Break any of those and the swap changes behaviour, not just cost - most often by evaluating a test whose precondition no longer holds.
solid answer
~40 sSwapping operands of a short-circuiting conjunction is a **semantics-preserving** change only under conditions worth naming explicitly. First, no dependency: if the second test was only well defined because the first one held, promoting it evaluates it in a world where its precondition is absent. Second, no required effect: an operand that was always evaluated may now be skipped for some inputs, and an operand that was usually skipped may now always run. Third, no failure or non-termination hiding behind the guard. When all three hold - both operands pure, total and independent - the truth value is identical for every input and only the expected work changes. Then order by expected cost: the cheap test that is usually decisive belongs first.
go deeper
Know that the order of the two operands decides which one may be skipped, so moving a test that protects another one in front of it is not a formatting change.
State the three conditions for a safe swap - no dependency, no needed effect, no failure hiding behind the guard - and explain why the unchanged truth table is not sufficient evidence of safety.
Reason about the swap on the inputs the current order shields the second operand from, and be ready to describe how a changed effect frequency would show up long after the change shipped.
Set the expectation that a precondition never rides on operand order: make it a branch or a named test, so correctness does not depend on a reader noticing which side of an operator something sits on.
## Why anyone reorders in the first place For a short-circuiting conjunction `a and b` with independent operands, the expected cost is > `cost(a) + P(a is true) * cost(b)` So the cheap test that is usually **false** earns its place on the left twice over: it is cheap to run, and it usually stops the expression before the expensive one is reached. A test that is expensive and usually true is the worst possible left operand - you always pay it and it rarely saves anything. This is real, and on a hot predicate the difference can dominate. The trap is that the rewrite looks purely cosmetic. Swapping two boolean operands does not change the truth table, so it reads like a free optimisation. It is free only under conditions that nobody writes down. ## The three hazards **1. Dependency.** The most common and most damaging. The right operand was written second because it is only *meaningful* when the left one held: a lookup after a presence test, an element access after a non-empty test, an operation on a narrowed shape after the test that established the shape. Promoting it evaluates it with its precondition unchecked. This does not produce a wrong boolean; it produces a failure, or worse, a plausible value computed from something that should never have been touched. **2. Effects.** An operand that does something - records, increments, populates, sends - has an evaluation frequency, and the swap changes it in one of two directions: - an operand that was on the left and always evaluated moves right and now runs only when the other test passes; - an operand that was on the right and often skipped moves left and now runs on every evaluation. Both are silent. Nothing fails, and the only symptom is a count somewhere that no longer means what it used to. **3. Failure and non-termination.** A guard can be the only reason an operand never raises or never hangs. Moving that operand in front of its guard exposes the very case the guard existed to exclude, typically on the rare inputs that made someone write the guard. ## When is the swap safe? | Property of the operands | Safe to swap? | What changes | |---|---|---| | Both pure, total, independent | Yes | expected cost only; result identical on every input | | Right operand depends on the left | No | the dependent test runs with its precondition unestablished | | Either operand has a needed effect | No | how often that effect happens | | Either operand can fail or not terminate | No | which inputs reach the failure | 'Pure' here means no observable effect and no dependence on anything the other operand changes; 'total' means it terminates and produces a value for every input it may now see - including the inputs the guard used to keep away from it. ## A checklist before the swap 1. Ask what makes the right operand well defined. If the answer mentions the left operand, stop. 2. Ask whether either operand does anything besides produce a boolean. If so, take that work out of the expression entirely rather than reordering around it. 3. Ask what each operand does on the inputs it has never seen - the ones the current order shields it from. 4. Only then estimate `P(a is true)` and the two costs, **from data rather than intuition**, and order accordingly. 5. Prefer making the dependency explicit - a nested branch, or a single test whose name states the compound condition - over relying on operand order to carry a precondition that only a reader who knows the domain can see. ## The judgment an interviewer is listening for Step 5 is the senior point. Operand order in a boolean expression is load-bearing but invisible: it carries a precondition that no type, no name and no comment states, and the next person to touch the expression - or a tool that reorders for readability - has nothing to warn them. Treating a fragile ordering as a design smell rather than a clever trick is what separates the answer that recites the rule from the answer that has cleaned up after it. And the cost model itself is only a model: probabilities measured on last quarter's data drift, and an ordering tuned to them quietly becomes the wrong one without anything breaking.
- Both operands are pure and always terminate. What does swapping them change?Only how often each one runs, and therefore the expected cost. The truth value is identical for every input, because conjunction is commutative and neither operand can observe or affect the other. The right order is then the one that minimises `cost(a) + P(a is true) * cost(b)` on real data - usually the cheap, usually-false test first.
- How would you keep a load-bearing operand order from being broken later?Take the dependency out of the expression. Nest the dependent test inside an explicit branch on the guard, or fold the pair into one named test whose name states the compound condition. Both make the precondition visible to a reader and to any tool that might otherwise reorder the operands as a readability change.
- What would you measure before choosing an order for a hot predicate?The per-call cost of each operand and how often the left one is true on production input. Those two numbers give the expected cost directly. Intuition is unreliable here because the cheap test is often assumed to be the selective one, and on skewed data it frequently is not.
saying these in an interview costs you the question
- Thinks swapping two pure operands can change the expression's truth value.
- Reorders a guard in front of nothing, exposing the case it protected.
- Ignores that a skipped operand's side effect simply stops happening.
- Orders by cost alone, never by how often the test is decisive.
- Calls operand order a cosmetic change because the truth table is unchanged.
- Assumes a measured probability stays valid as the data shifts.