A compound condition's second operand also writes to a log — when does short-circuit evaluation skip it?
answer
- the result may already be decided
- not every operand is evaluated
- AND stops on a false left operand
- OR stops on a true left operand
- a skipped effect never happens
basics
~20 sShort-circuit evaluation stops as soon as the result is decided: a short-circuiting AND skips its second operand when the first is false, an OR when the first is true. The skipped operand never runs, so its log write never happens.
solid answer
~50 sA short-circuiting boolean operator is a control-flow construct, not a two-argument function. It evaluates the left operand first and asks whether that value already settles the whole expression: `false AND anything` is false, and `true OR anything` is true, so in those two cases the right operand is never evaluated at all. That matters the moment the right operand does something other than produce a value — a log write, a counter increment, a cache fill — because the effect happens only on the inputs that reach it. The skip is also the point of the common guard idiom, where the left operand checks that the data exists and the right operand is only safe to evaluate because that check passed. If you need the effect unconditionally, take it out of the condition and run it on its own line.
code
pseudocode · 7 linesfunction isAllowed(request)
# AND below is short-circuiting: the right operand runs only if the left is true
# recordAttempt increments an audit counter, then returns a boolean
return hasValidToken(request) AND recordAttempt(request)
# hasValidToken false -> recordAttempt is not evaluated, counter unchanged
# hasValidToken true -> recordAttempt is evaluated, counter incrementedgo deeper
Be able to say, without hesitating, which operand is skipped for AND and for OR, and that a skipped operand does not run at all.
Explain the condition as control flow: the operator decides whether the right operand's code executes, so an effect placed there runs only on some inputs. Name the guard idiom as the deliberate use of that.
Show the production consequence — a metric that counts fewer events than it should, a cache warmed only on one path — and describe how you would find it by reading which operand holds the effect.
Decide the team standard: whether effects are allowed inside conditions at all, and what a review should ask when a condition contains a call rather than a comparison.
## The mechanism A **short-circuiting** boolean operator does not take two values and combine them. It takes one value and one *piece of unevaluated code*, and decides whether that code runs. It evaluates the left operand, asks whether the result of the whole expression is already determined, and evaluates the right operand only if it is not. - A short-circuiting **AND** is settled by a **false** left operand: false combined with anything is false, so the right operand is skipped. - A short-circuiting **OR** is settled by a **true** left operand: true combined with anything is true, so the right operand is skipped. | Operator | Left operand | Right operand | Result of the condition | |---|---|---|---| | AND | false | not evaluated | false | | AND | true | evaluated | the right operand's value | | OR | true | not evaluated | true | | OR | false | evaluated | the right operand's value | Read that table as a statement about **which code runs**, not only about which value comes out. The value column is what a truth table would tell you; the middle column is the part that belongs to control flow, and it is the part interviews probe. ## Why it matters that an operand is code An operand that only reads data and returns a boolean is **effect-free**: skipping it changes nothing an outside observer can detect, so short-circuiting is invisible and looks like an optimisation. An operand that also changes something observable — appends a line to a log, increments a counter, stores a value, sends a message — is not effect-free, and now the skip is **semantics**, not speed. That gives a simple rule for reading a condition: for every operand, ask *what does this do besides produce true or false?* If the honest answer is "nothing", the condition is a pure test. If the answer names an effect, the condition is also a piece of control flow that runs that effect on some inputs and not others. - A counter placed in the right operand of an AND counts only the inputs that passed the left test — not all inputs. - A log line placed in the right operand of an OR is written only for the inputs the left test did **not** already accept. - A lazily built cache entry in the right operand is populated only on the inputs that reach it, which is often what you wanted and occasionally a bug. ## The guard idiom: the skip is the point The most common deliberate use of short-circuiting is a **guard**: the left operand establishes the precondition that makes the right operand legal to evaluate at all. ``` if record exists AND record.score > threshold then accept() ``` If both operands were always evaluated, the right one would be asked for a field of something that is not there. The guard works *because* evaluation is ordered and conditional, which is why the two operands cannot be reordered freely and why a condition is not the same thing as a truth table. Languages differ in how much of this they hand you — some provide a second, non-short-circuiting boolean operator that always evaluates both operands, and some do not — but the short-circuiting form is the one every language's ordinary boolean operator uses. ## Getting a skipped effect back When the effect must happen on every input, do not try to fix it inside the condition. In order of preference: 1. **Hoist the effect out.** Compute it on its own statement before the condition, bind the boolean it returns to a name, and use that name as the operand. The effect is now unconditional and the condition is a pure test again. 2. **Split the condition into nested selection.** If the effect belongs only on one path, make that path explicit rather than hiding it in the right-hand operand of an operator. 3. **Use a non-short-circuiting operator**, where the language offers one and the operands are cheap and safe to evaluate in either state. This is the weakest option, because the next reader has to notice that the operator is the unusual one. ## What an interviewer is listening for The memorised sentence — "it stops early" — is the floor. The answer that scores is the one that treats the condition as control flow: naming which operand is skipped for each operator, saying that the skip is observable exactly when the operand has an effect, and pointing out that the guard idiom depends on the skip rather than tolerating it.
- The log write must happen for every request. How do you restructure the condition?Take it out of the condition. Call the logging operation on its own statement before the selection, bind the boolean it returns to a local name, and use that name as the operand. The effect is then unconditional and the condition becomes a pure test that any reader can reorder safely.
- Besides avoiding work, what does short-circuit evaluation let you write that you otherwise could not?A guard. The left operand establishes the precondition — the record exists, the collection is non-empty, the index is in range — that makes the right operand legal to evaluate. With unconditional evaluation of both operands you would need a nested selection to express the same thing, because the right operand would be evaluated in the state the guard was meant to exclude.
- Does short-circuiting change the truth value the condition finally produces?No — the value matches what a full truth table gives, because the operator only skips an operand whose value could not change the outcome. What it changes is which operands were evaluated, and therefore which effects occurred. That difference is invisible for effect-free operands and load-bearing for everything else.
saying these in an interview costs you the question
- Says both operands always run and the operator just picks a result
- Calls short-circuiting a pure optimisation that is never observable
- Claims an OR short-circuits when its first operand is false
- Treats a side-effecting call in a condition as an ordinary test
- Says operand order in a condition can never change behaviour
- Thinks a skipped operand's effect is undone rather than never run