# Short-Circuit Evaluation

> Why && and || stop evaluating the moment the answer is already determined, and how that becomes both a useful coding pattern and a source of subtle bugs.


---

# Short-Circuit Evaluation

You've written `if (user && user.name)` without thinking twice about it. But have you ever asked what happens if `user` is `null` - does `user.name` still get evaluated, and crash? It doesn't, and the reason it doesn't is a rule built into nearly every programming language: `&&` and `||` stop evaluating the instant they already know the answer. That behavior has a name - **short-circuit evaluation** - and it's doing more work in your code than you probably realize, for better and for worse.

## How to read this

Read in order. Phase 1 is the rule itself, in its simplest form. Phase 2 shows the two everyday patterns this rule enables - patterns you've almost certainly used already. Phase 3 is the part worth slowing down for: the gotchas that show up when short-circuiting interacts with side effects and falsy values.

## The phases

1. [Why bother checking the second half](01-the-core-rule.md) - the rule: AND stops at the first false, OR stops at the first true.
2. [Where this becomes a real pattern](02-real-patterns.md) - guard checks and default values.
3. [The gotcha](03-the-gotcha.md) - skipped side effects, and the falsy-value surprise with `||`.


---

# Why bother checking the second half

Picture a light switch wired to two switches in series - both have to be flipped on for the light to turn on. The moment you see the first switch is off, you already know the light is off. You don't need to walk over and check the second switch at all; the outcome is settled. That's the entire idea behind short-circuit evaluation, applied to `&&` and `||` in code.

If you want the background on what AND and OR mean as logical connectives before diving into this specific behavior, [Propositional Logic](/guides/propositional-logic) covers that foundation - this guide picks up from there and focuses on one particular thing your language does when it *evaluates* an AND or OR expression at runtime.

## The rule for AND

For `&&` (or `and`, depending on the language), the whole expression is `true` only if *both* sides are `true`. That means the instant the left side turns out to be `false`, the answer is already locked in - the entire expression must be `false`, no matter what the right side would have been. So the language doesn't bother evaluating the right side at all.

```text
false && (anything)   ->  always false, right side never runs
true  && (right side) ->  right side must be checked to know the answer
```

*What just happened:* the left operand alone was enough to determine the result in the first row. There was no point running the right side, so the language skipped it entirely - not "evaluated it and ignored the result," but genuinely never executed it.

## The rule for OR

`||` (or `or`) works the mirror-image way. The whole expression is `true` if *either* side is `true`. So the instant the left side turns out to be `true`, the answer is already locked in as `true` - the right side is skipped.

```text
true  || (anything)   ->  always true, right side never runs
false || (right side) ->  right side must be checked to know the answer
```

*What just happened:* same idea, flipped. One `true` on the left is enough to guarantee a `true` result, so there's nothing left for the right side to contribute.

## Seeing it happen

Here's the rule made visible, using a function call that prints a message so you can see whether it actually ran:

```python runnable
def loud_true():
    print("loud_true ran")
    return True

def loud_false():
    print("loud_false ran")
    return False

print("--- AND with a false first ---")
result = loud_false() and loud_true()
print("result:", result)

print("--- OR with a true first ---")
result = loud_true() or loud_false()
print("result:", result)
```

*What just happened:* in the AND example, only `"loud_false ran"` prints - `loud_true()` never executes, because `and` already knew the answer was `False` after the left side came back `False`. In the OR example, only `"loud_true ran"` prints, for the same reason in reverse. If short-circuiting weren't happening, both function calls would print every time, regardless of order.

> Short-circuiting isn't an optimization trick bolted on afterward - it's the definition of how `&&` and `||` evaluate. The right side runs *only if the left side didn't already settle the answer.*

## Why this matters beyond trivia

Right now this might look like a curiosity about how your language saves a bit of work. It's more than that: because the right side is *guaranteed* not to run when it's unnecessary, you can rely on that guarantee to write code that would otherwise crash. That's the entire subject of Phase 2 - the guard pattern and default values are both just this one rule, used on purpose.

Watch it animated: [short-circuit evaluation](/explainers/ShortCircuit.dc.html)

```quiz
[
  {
    "q": "In `false && someFunction()`, does someFunction() get called?",
    "choices": [
      "Yes, always - && only skips the return value",
      "No - the left side being false already determines the result, so the right side never runs",
      "Only if someFunction() has no arguments",
      "It depends on the return type of someFunction()"
    ],
    "answer": 1,
    "explain": "AND short-circuits on a false left side: the overall result is already false, so the right side is never evaluated."
  },
  {
    "q": "In `true || someFunction()`, does someFunction() get called?",
    "choices": [
      "Yes, OR always evaluates both sides",
      "No - the left side being true already determines the result, so the right side never runs",
      "Only in compiled languages, not interpreted ones",
      "Only if the function returns a boolean"
    ],
    "answer": 1,
    "explain": "OR short-circuits on a true left side: the overall result is already true, so evaluating the right side would be redundant, and it's skipped."
  }
]
```


---

# Where this becomes a real pattern

Phase 1 established the rule. Here's where it stops being trivia and starts being something you lean on every day, usually without stopping to name it.

## The guard pattern

Say you have a `user` variable that might be `None`/`null`, and you want to print their name - but only if a user actually exists. Without short-circuiting, `user.name` would need `user` to be checked separately first, or it crashes trying to look up `.name` on nothing.

```python runnable
user = None

# without a guard, this line would crash:
# print(user.name)   -> AttributeError: 'NoneType' object has no attribute 'name'

# with the guard pattern:
print(user and user.name)
```

*What just happened:* `user` is `None`, which is falsy, so `and` already knows the whole expression is falsy - it stops right there and never touches `user.name`. Nothing crashes. If `user` had actually been an object with a `name` attribute, `and` would have needed to check the right side, evaluated `user.name`, and returned that instead.

This is the same shape as the "optional chaining" you may have seen as dedicated syntax in some languages (`user?.name` in JavaScript, for instance) - that syntax exists precisely to express this guard pattern more directly, but the underlying idea, checking that something exists before reaching into it, is exactly what `&&` was already doing.

```text
user && user.name
  |          |
  |          +-- only reached if user is truthy
  +-- checked first; if falsy, stops here
```

You'll see this chained further in real code too - `settings && settings.theme && settings.theme.color` - each `&&` guarding the next step from running on something that might not exist.

## Default values with OR

The mirror-image pattern uses `||` to supply a fallback when a value is missing.

```python runnable
def greet(name):
    display_name = name or "friend"
    print(f"Hello, {display_name}!")

greet("Amara")
greet(None)
greet("")
```

*What just happened:* when `name` is `"Amara"` - truthy - `or` already knows the result is truthy after checking the left side, so it short-circuits and returns `"Amara"` without even looking at `"friend"`. When `name` is `None`, the left side is falsy, so `or` has to check the right side, and returns `"friend"`. That's the pattern: `value or fallback` returns `value` if it's truthy, otherwise falls back.

Look closely at the third call, though - `greet("")`. An empty string is falsy too, so `or` falls back to `"friend"` even though the caller *did* provide a name; it just happened to be empty. That's not a bug in this particular example, but it's worth noticing now, because it's exactly the shape of problem Phase 3 digs into.

## Why both patterns are the same trick

Notice that the guard pattern and the default-value pattern are not two different features - they're the identical short-circuiting rule from Phase 1, aimed at two different problems. `&&` short-circuits on the first falsy value, which is useful when falsy means "stop, nothing more to check." `||` short-circuits on the first truthy value, which is useful when truthy means "good enough, use this." Once you see them as the same mechanism, you'll start noticing this rule everywhere - configuration loading, default arguments, permission checks - not just in the two examples above.


---

# The gotcha

Everything in Phase 2 relied on the right side being skippable without consequence - checking `user.name` versus not checking it doesn't change anything else in your program. That assumption quietly breaks in two specific situations, and both are worth knowing before they cost you an afternoon of debugging.

## Skipped side effects

A **side effect** is anything a function call does beyond returning a value - writing to a database, sending a request, incrementing a counter, printing a log line. Short-circuiting doesn't know or care whether the right side has side effects. It just skips it. If that skipped call was supposed to *do* something, that something never happens.

```python runnable
def save_to_database():
    print("saving to database...")
    return True

is_admin = False

# looks like it might save, but does it?
is_admin and save_to_database()
```

*What just happened:* nothing printed. `is_admin` is `False`, so `and` already knows the result is falsy and never runs `save_to_database()` - the save never happened, silently. If the intent here really was "only save if the user is an admin," this code is correct. But if someone wrote `is_admin and save_to_database()` expecting the save to happen unconditionally, or misjudged what `is_admin` would be, the bug is invisible: no error, no crash, just a function that silently never ran.

> The danger isn't short-circuiting itself - it's relying on a side-effecting call showing up on the right side of `&&`/`||` as if it were guaranteed to run. It's only guaranteed to run when the left side doesn't already settle the answer.

The practical guard: if a function call needs to happen no matter what, don't put it on the right side of `&&`/`||` and hope. Call it on its own line, or use an explicit `if`.

## The falsy-value surprise with OR

Phase 2 ended with a hint of this. `value or fallback` doesn't check "is `value` missing" - it checks "is `value` falsy," and those are not the same question. In most languages, `0`, `""` (empty string), and sometimes other values like an empty list are all falsy, exactly like `None`/`null`/`undefined`.

```python runnable
def set_volume(level):
    volume = level or 10   # 10 is the "default" if no level given
    print(f"volume set to {volume}")

set_volume(0)     # user explicitly wants silence
set_volume(None)  # user didn't specify anything
```

*What just happened:* `set_volume(0)` should set the volume to `0` - the user asked for silence. Instead it prints `volume set to 10`, because `0` is falsy, so `or` treats it exactly like a missing value and falls back to the default. The caller's *actual, intentional* `0` got silently overridden. This is one of the most common real-world bugs traced back to `||`: any legitimate falsy value - `0`, `""`, an empty array - triggers the fallback whether you wanted it to or not.

## The fix: nullish coalescing

Several languages now provide an operator specifically to solve this - usually spelled `??`, and called **nullish coalescing**. Instead of falling back on *any* falsy value, it falls back only when the left side is genuinely `null` or `undefined` (or that language's equivalent of "nothing was there"), leaving `0`, `""`, and `false` alone as the real values they are.

```text
level || 10   ->  falls back on 0, "", false, null, undefined - anything falsy
level ?? 10   ->  falls back only on null / undefined - real 0 and "" survive
```

*What just happened:* `??` narrows the question from "is this falsy" to "is this actually missing," which is almost always what people meant when they reached for `||` as a default-value shortcut in the first place. Python doesn't have a dedicated `??` operator, but the distinction still matters there - you'd write an explicit `if level is not None` check instead of leaning on `or` when zero and empty-string are valid inputs.

## Carrying this forward

Short-circuiting itself was never the bug in either example - it's a precise, predictable rule, exactly as described in Phase 1. The bug is always in the assumption layered on top of it: assuming a call will run, or assuming "falsy" means "absent." Once you know to ask those two questions - *does the right side need to run no matter what, and could a legitimate falsy value show up here* - you'll catch both of these before they ship instead of after.
