skip to content

questions

4

What does the `if` guard on a `match` statement's `case` clause do?

level: juniorimportance: must knowfreq 42%

answer

  1. An extra condition attached to one case
  2. Runs only when the pattern already matched
  3. Sees the names the pattern just bound
  4. Falsy guard falls through to the next case

basics

~20 s

A guard is an extra if expression written after a case pattern. It is checked only once that pattern has matched and bound its names; a falsy guard rejects the case and matching moves on to the next one.

solid answer

~40 s

A `case` clause may end with `if <expr>`, called a guard. The interpreter first tries the pattern; only if the pattern matches — and therefore after its capture names are bound — does it evaluate the guard, so the guard can read those names. A truthy guard commits to that case body; a falsy guard rejects the case and matching moves on to the next `case` in source order. The guard is an ordinary boolean expression, not a pattern: it can call functions, compare two captured names against each other, or use the walrus operator. That is exactly why guards exist — patterns can only test structure and equality against constants, so any relational or computed condition (`x > y`, `len(name) > 8`) has to live in a guard.

code

python · 15 lines
python
def classify(point):
    match point:
        case (x, y) if x == y:
            return "diagonal"
        case (x, y) if x > y:
            return "below"
        case (x, y):
            return "above"
        case _:
            return "not a pair"

print(classify((3, 3)))   # diagonal
print(classify((5, 2)))   # below
print(classify((1, 9)))   # above
print(classify(7))        # not a pair

go deeper

for a junior

Recall the shape case <pattern> if <condition>: and the order: pattern first, then guard, and a falsy guard means the next case gets a try. Be ready to say why x > y cannot be written as a pattern.

for a middle

Explain the mechanics: guards see the names the pattern bound, run once per case in source order, and are ordinary expressions that may call functions. Contrast a guard with an if in the case body and name the fall-through difference.

for a senior

Show the production judgment — keep guards pure and cheap because an earlier case's guard still runs before a later case wins, and never read a capture name outside the body that bound it, since failed guards leave bindings behind.

for a principal

Own the readability call: when a chain of heavily guarded cases has stopped being structural dispatch and should be a plain if/elif chain or a lookup table, and how you would set that convention for a codebase adopting match.

## The syntax Python's `match` statement, added in 3.10 by PEP 634, matches a subject against a series of `case` clauses. Each `case` carries a **pattern**, and may optionally end with a **guard**: the keyword `if` followed by any expression. ```python match point: case (x, y) if x == y: print("on the diagonal") case (x, y): print("off the diagonal") ``` The guard is not part of the pattern. It is a plain expression evaluated for truthiness, exactly like the condition of an ordinary `if` statement. ## The order of operations For each `case`, in source order, the interpreter does two things: 1. **Try the pattern.** If it does not match, the guard is never evaluated at all and matching moves to the next `case`. 2. **If the pattern matched, evaluate the guard.** By this point every capture name in the pattern has been bound, so the guard can read them. A truthy result commits to that case's body and the whole `match` ends there; a falsy result rejects this case and matching continues with the next one. That ordering is the single most important fact about guards, and it is what makes them useful. A pattern can only test *shape* and equality against constants; it cannot express `x > y`, `len(s) > 8`, or `value in allowed`. The guard runs late enough that the destructured pieces already exist as names, so relational and computed conditions become easy: ```python match command.split(): case [verb, arg] if verb in {"open", "close"}: ... ``` ## What a guard may contain Anything a normal expression may contain: function calls, comprehensions, chained comparisons, a walrus assignment. Two consequences follow. First, **a guard can have side effects**, and because matching walks the cases in order, a guard belonging to an earlier case runs even though that case ultimately loses. If two overlapping cases both call the same expensive helper in their guards, that helper runs twice for one subject. Keep guards cheap and pure where you can. Second, **names bound by a pattern stay bound even when its guard rejects the case**. Pattern matching binds as it goes and does not roll back, so after a failed guard the captured names are still visible in the enclosing scope — with values from a case that did not win. Never read a capture name outside the body of the case that bound it. ## What a guard can see The guard is evaluated in the ordinary scope of the surrounding code, so it can read anything a normal expression could: locals, globals, closure variables, `self`, and — crucially — the names this pattern has just bound. What it cannot do is *introduce* a pattern binding. A guard is a filter, not a matcher: it decides yes or no about a case whose destructuring has already happened. If you find yourself wanting a guard to reach further into the subject, the right move is usually a richer pattern rather than a longer guard. ## Guards on other pattern kinds A guard may follow any pattern, including the wildcard. `case _ if retries > 3:` is a legal, conditional catch-all — and note that it is no longer irrefutable, so a later `case` after it is still reachable. A guard may also follow an or-pattern; there is one guard per `case`, evaluated once after whichever alternative matched, not once per alternative. Combined with an `as` pattern, `case ("GET" | "HEAD") as verb if verb in allowed:` reads naturally: alternatives match, `verb` binds, guard decides. ## Guards versus an `if` in the body You can always push the condition into the body instead: ```python case (x, y): if x == y: ... ``` The behaviour differs in an important way. A guard that fails **falls through to the remaining cases**; an `if` inside the body does not — the `match` has already committed to that case, and the other cases are dead. Use a guard when a failed condition should let a later, more general case handle the subject; use a body `if` when the case is definitely the right handler and the condition is only an internal detail. ## Style Guards are best kept to a short, obviously-pure boolean expression over names the pattern just bound. Long guards are a signal that the discrimination belongs in the pattern (or that the code wants a plain `if`/`elif` chain instead of a `match`). None of this changed between 3.10 and 3.14: guards behave the same on every version that has `match`.

  • If a guard on a `case` clause is falsy, what happens to the names that clause's pattern already bound?
    They stay bound. Pattern matching binds names as it destructures and never rolls them back, so after a rejected guard the captures are still visible in the enclosing scope, holding values from a case that did not win. Only read a capture inside the body of the case that bound it; treat anything else as a bug waiting to happen.
  • Why not just put the condition in an `if` inside the case body instead of writing a guard?
    Because the fall-through differs. A failed guard rejects the case and lets the remaining `case` clauses try the subject; an `if` inside the body runs after `match` has already committed, so the later cases are unreachable. Use a guard when a later case should get its turn, and a body `if` when this case is definitely the handler.
  • Can a guard follow the wildcard pattern `case _`, and does that keep the catch-all a catch-all?
    Yes, `case _ if attempts > 3:` is legal. Adding a guard makes the case refutable, so it is no longer an unconditional catch-all and a following `case` clause is still reachable — unlike a bare `case _`, which must be last because the compiler treats anything after it as unreachable.

The pattern is the bouncer checking the shape of your ID; the guard is the second check on the details it just read off it. Fail the second check and you rejoin the queue for the next door, not the street.

saying these in an interview costs you the question

  • Says the guard is evaluated before the pattern is tried
  • Thinks a falsy guard ends the whole match statement
  • Believes a failed guard unbinds the names already captured
  • Treats the guard as part of the pattern that can bind names
  • Claims a guard may only compare against literal constants

context

open as a page

Why must every alternative of a `match` or-pattern bind the same names?

level: middleimportance: should knowfreq 34%

basics

~20 s

Because the case body must be able to read every captured name no matter which alternative matched, and Python has no maybe-unbound state. The compiler enforces it, raising SyntaxError: alternative patterns bind different names before the code ever runs.

open as a page

Why does a `match` guard's helper call run twice per record in a geocoding batch?

level: seniorimportance: should knowfreq 26%

basics

~20 s

Because guards are evaluated per case, in source order, until one succeeds. Two overlapping cases that both call the lookup in their guards call it twice: the first case's guard ran and paid its side effect before rejecting the record.

open as a page

What does an `as` pattern such as `case [x, y] as pair` bind in a `match`?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

An as pattern binds the name on its right to the value its left-hand pattern matched, on top of any names that pattern captured. At the top level that value is the whole subject; nested, it is only the sub-value.

open as a page