skip to content

In a Python match statement, how often is the subject expression evaluated?

level: middleimportance: should knowfreq 45%

answer

  1. Compare it with repeated separate comparisons
  2. Count the calls, not the cases
  3. One value sits on the stack
  4. A costly call in the header is safe

basics

~20 s

Exactly once. Python evaluates the expression written after the match keyword before any case is tried, then tests that one value against each pattern in turn, so an expensive or side-effecting call runs a single time.

solid answer

~40 s

The subject is evaluated once, up front, and the resulting object is what every pattern is tested against — a disassembly shows one load of the subject followed by a copy before each test. That makes `match parse_record(line):` safe in a way a hand-written ladder of separate comparisons is not, because each branch of a ladder re-invokes whatever it compares. The evaluation happens even when no case matches, so its side effects occur regardless of the outcome. Two details follow from the grammar: the header takes a comma-separated expression list, so `match x, y:` builds and matches the tuple `(x, y)`, and `match x,:` builds a one-item tuple; and guard expressions, written after `if` on a case, are the only per-case code that runs, and only for a case whose pattern already matched.

code

python · 14 lines
python
calls = 0

def read_flag():
    global calls
    calls += 1
    return "ok"

match read_flag():
    case "ok":
        status = 0
    case _:
        status = 1

print(status, calls)

go deeper

for a junior

Recall the headline fact: whatever you write after the match keyword runs once, before any case is checked. It is safe to call a function there instead of storing the result in a temporary variable first.

for a middle

Explain the mechanics: one evaluation, one object tested against each pattern in order, side effects happening even when nothing matches, and a comma in the header quietly building a tuple subject.

for a senior

Bring the production angle — an impure or expensive subject such as a parse or a queue read is called exactly once, which is a real correctness argument in code where two evaluations could disagree. Mention that guards are the per-case code with side effects.

for a principal

Own the guidance: when a team is choosing between match and other branching for hot or side-effecting code, the single-evaluation guarantee and the absence of jump-table dispatch are the two facts that should drive the call, not syntax preference.

## The rule PEP 634 specifies that the subject of a `match` statement — the expression between the `match` keyword and the colon — is evaluated **once**, before any pattern is considered. The resulting object is then held and tested against each `case` pattern from the top down until one matches or the cases run out. This is one of the concrete reasons `match` is not merely sugar over a chain of comparisons. A ladder that compares the *result of a call* in several branches calls it in several branches; a match calls it once. ## Why it matters in real code Consider a genome-annotation pipeline whose workers classify one parsed record at a time. The subject is a call: it parses a line into a record, and that parse is neither cheap nor pure — it consults a locale-dependent date format for the record's release field, so calling it twice under a different locale can even yield two differently shaped values. Written as a match, the parse happens once per line and every pattern sees the same object. Written as a ladder of comparisons that each re-parse, you pay the cost several times and you open a window in which two branches disagree about what the record contains. The same argument applies to any subject that advances state: a call that pulls from an iterator, reads a socket, increments a metric, or writes a log line. With a match you can reason about it as a single event. ## Side effects happen even when nothing matches Evaluation precedes matching, so the subject's side effects occur regardless of the outcome. If every pattern fails, the match statement does nothing at all — but the call in its header has already run. Reviewers sometimes read "no case matched, so nothing happened" and are wrong about the header. ## The comma trap The grammar after `match` accepts a comma-separated expression list, exactly as `return` does. So `match x, y:` does not match two subjects — it builds the tuple `(x, y)` and matches that. `match x,:` builds a one-item tuple `(x,)`. This is occasionally what you want, and matching several values at once as a tuple is a genuinely idiomatic use. It is a trap only when the comma is a typo, because the code still runs and simply matches something you did not intend. A related consequence: because the subject can be any expression, it may contain a walrus assignment, a call, a comprehension or a conditional expression. All of that is evaluated once, in the header, under the ordinary evaluation rules. ## What does run per case Patterns themselves do not evaluate arbitrary code — they inspect the already-evaluated subject. The exception is a guard, the `if` clause a case may carry: a guard runs only after that case's pattern has matched and bound its names, and only for that case. So the count is one subject evaluation, plus at most one guard evaluation per case actually reached, in order, stopping at the first case that both matches and passes its guard. ## Under the hood Disassembling a small function containing a match shows the shape directly: the subject is loaded once, then a `COPY` puts a duplicate on the stack before each comparison, with conditional jumps threading the cases together. There is no hash-based jump table and no dictionary lookup — CPython emits a straight-line ladder of tests over a single copy of the subject. That means the guarantee is structural rather than an optimisation, and it also means a match with many cases costs a sequence of tests, not a constant-time dispatch. ## A temporary variable is not the same thing A fair objection is that you could always assign the subject to a temporary name first and compare against that. You could, and in pre-3.10 code that is exactly what a ladder of comparisons had to do to stay correct. The difference is that the match statement makes single evaluation a property of the construct rather than a discipline the author must remember: the expression appears in exactly one place, so no later edit can slip in a second call the way adding one more branch to a comparison ladder can. ## How to answer this in an interview Say "once, before any pattern is tried", then earn the follow-up by naming the consequences: a costly or impure subject is safe; side effects still happen when nothing matches; a stray comma in the header silently builds a tuple; guards, not patterns, are the per-case code. If you can add that a disassembly shows one load and a `COPY` per test, you have shown you checked rather than assumed.

  • Is the subject expression still evaluated when no case matches?
    Yes. Evaluation happens before matching begins, so the header's side effects occur even if every pattern fails and the statement does nothing. That matters when the subject consumes from an iterator, advances a cursor, or emits a log line — reviewers who read "nothing matched, so nothing happened" are wrong about the header.
  • What does the statement `match x, y:` actually match against?
    Against the two-item tuple `(x, y)`. The grammar after `match` accepts a comma-separated expression list, exactly as `return x, y` does, so the comma builds a tuple. `match x,:` builds a one-item tuple. Matching several values at once this way is idiomatic; the hazard is a comma typed by accident, because the code still runs.
  • Which code inside a match statement does run once per case?
    Only guard expressions — the `if` clause a case may carry. A guard is evaluated after that case's pattern has matched and bound its names, and only for that case, so guards with side effects run in case order up to the first case that both matches and passes. Patterns themselves only inspect the already-evaluated subject.

saying these in an interview costs you the question

  • Says the subject is re-evaluated for every case tested
  • Assumes match compiles to a hash-based jump table
  • Thinks a failed match skips the subject's side effects
  • Reads match a, b: as matching two separate subjects
  • Believes guards run before any pattern is tested

context