skip to content

A match statement in a telemetry collector routes every reading to its final case — how do you diagnose it?

level: seniorimportance: nice to knowfreq 18%

answer

  1. One branch claims every input
  2. That is not an off-by-one
  3. The last case is the unguarded one
  4. Look for a missing dot
  5. Add a case after it and recompile

basics

~20 s

A branch that fires for every input is a capture pattern: the final case names a constant without a dot, so it matches anything and rebinds the name. Adding a case after it makes the module refuse to compile.

solid answer

~50 s

Start from the shape of the symptom: a branch that claims *every* subject is not an off-by-one in a comparison, it is a pattern that cannot fail. In practice that means the final case is a bare name — often after a refactor turned `import thresholds` plus `case thresholds.WARN:` into `from thresholds import WARN` plus `case WARN:`. A bare name is a capture pattern, so it matches anything and binds the subject; it survives compilation only because nothing follows it. Confirm it two ways: read the case for a dot, and add a throwaway `case _: pass` after it — if it was a capture, the file now fails to compile with a name-capture error saying the remaining patterns are unreachable. `dis.dis` shows the same thing as a store rather than a comparison. Fix it structurally by moving the constants to an `enum.Enum` and matching `case Level.WARN:`.

code

python · 17 lines
python
import enum

class Level(enum.Enum):
    OK = 0
    WARN = 1
    CRITICAL = 2

WARN = Level.WARN

def route(level):
    match level:
        case Level.CRITICAL:
            return "page"
        case WARN:
            return "log"

print(route(Level.CRITICAL), "|", route(Level.WARN), "|", route(Level.OK))

go deeper

for a junior

Recall the underlying rule that makes this possible: a bare name after case matches everything and binds it. You are not expected to run the triage, only to recognise the pattern in a snippet.

for a middle

Explain the mechanics that let it reach production: capture patterns are irrefutable, and CPython only rejects one when other cases follow it, so a final bare-name case compiles cleanly and swallows everything.

for a senior

Demonstrate the triage. Say why a universal branch is categorically not an off-by-one, use a compile-time probe or a disassembly as objective confirmation, explain why the existing suite was blind, and land a structural fix rather than a patch.

for a principal

Own the systemic answer: constants used in dispatch belong on enums, imports in matching modules stay module-qualified, suites are measured by branch coverage rather than fixture count, and the review rule about dotted names is written down once for everyone.

### The shape of the bug A sensor-telemetry collector classifies each incoming reading into a bucket and the dispatch is a `match` statement. After a refactor that replaced `import thresholds` with `from thresholds import WARN`, the final case was tidied from `case thresholds.WARN:` to `case WARN:`. That one edit changes the case from a **value pattern** (dotted name, looked up at match time and compared with `==`) into a **capture pattern** (a bare name, which always matches and binds the subject). Every reading that survived the earlier cases now lands in the last branch, and the module still compiles, because CPython's irrefutable-pattern check only fires when a capture pattern has cases **after** it — this one is last. The first report was misleading in the way production reports usually are: a reading sitting exactly on the warn boundary was filed one bucket too high, so the team spent a day hunting an off-by-one in the comparison logic. There is no comparison left to be off by one. The boundary sample was simply the first one anybody looked at closely; every sub-critical reading was being filed as `WARN`. ### Why the tests stayed green The regression pack was 340 recorded readings replayed from a past incident, and an incident replay is by construction full of elevated readings. Almost every fixture legitimately expected `WARN` or above, so a branch that returns `WARN` for everything passed 340 out of 340. The pack was a strong-looking suite that had no sample capable of distinguishing the bug — the test-design lesson being that a dispatch table needs at least one fixture per branch *and* one that must not reach the catch-all. ### Confirming it in minutes * **Read the case for a dot.** Every case that means "compare against a named constant" contains a dot. A bare name means capture. This is a pure reading rule and it settles the question outright. * **Probe with the compiler.** Add a throwaway `case _: pass` after the suspect case and re-import the module. If the case was a capture pattern, the file now refuses to compile with a name-capture error saying the remaining patterns are unreachable. If it compiles, the case really was a value pattern. This is the fastest objective confirmation and needs no debugger. * **Disassemble.** `dis.dis` on the function shows the suspect case storing into a name rather than loading an attribute and comparing — the store is the capture. * **Check the name afterwards.** A capture rebinds. Inside a function the captured name is a local, so the imported constant appears to hold the last subject seen wherever that local is read; at module level the constant itself is overwritten. * **Feed it a value that must not match.** One reading from a bucket the branch should never claim, run through the dispatch, is the behavioural proof. ### The fix Give the constant a dotted path, which is the only spelling that compares: * Make the buckets an `enum.Enum` and write `case Level.WARN:`. Enum members compare by identity because `Enum` does not define `__eq__`, and the enum gives you the dot for free. * Or keep the module import — `import thresholds` — and write `case thresholds.WARN:`. Avoid `from thresholds import WARN` in a file that uses `match`, precisely because it strips the dot. * Or hang the constants on a small class or a `types.SimpleNamespace` and match on the attribute. Then close the hole in the suite: one fixture per bucket, plus a reading whose expected bucket is the *first* case, so a universal-match branch fails immediately. ### Preventing the class of defect The reviewable rule is one sentence: **in a `case`, a name without a dot binds, a name with a dot compares.** Adopt it as a review checklist item, prefer enums over loose module constants for anything that appears in a `match`, and be suspicious of any `from module import CONSTANT` in a file that dispatches. A static type checker will also flag a `case` name that shadows a module-level constant, and a catch-all `case _:` placed at the end has the useful side effect of turning an accidental capture pattern above it into a compile error rather than a silent behaviour change. ### The senior signal What an interviewer is listening for is not the trivia that a bare name captures — that is the middle answer. It is the diagnostic sequence: recognise that a branch which fires for *every* input is categorically different from an off-by-one, reach for a compile-time probe rather than a print statement, explain why the suite could be large and still blind, and finish with a structural fix (enum plus dotted match) rather than a one-line patch.

  • Why did a 340-case regression pack fail to catch this?
    The pack replayed a past incident, so nearly every recorded reading legitimately belonged in the elevated bucket that the runaway branch returns. A suite can be large and still have no fixture able to distinguish the bug. The repair is coverage by branch: at least one case per branch, plus a sample whose expected result is the *first* case, so a universal-match branch fails immediately.
  • Why did the module compile at all if a capture pattern is such a trap?
    CPython rejects an irrefutable pattern only when cases follow it, because those become unreachable. A capture pattern in the final position makes nothing unreachable, so it compiles cleanly. That is exactly why the last case is where this defect hides, and why ending dispatch with an explicit `case _:` turns a future accidental capture above it into a compile error.
  • What review rule would prevent the class of defect?
    In a case, a name without a dot binds and a name with a dot compares — so any case meant to test a constant must contain a dot. Practically: keep dispatch constants on an `enum.Enum`, prefer `import module` over `from module import CONSTANT` in files that use match, and end every dispatch with an explicit catch-all.

saying these in an interview costs you the question

  • Chases an off-by-one when one branch claims every input
  • Assumes a large green test suite rules out a dispatch bug
  • Says the interpreter would have warned about the bare name
  • Fixes it by reordering cases rather than dotting the name
  • Thinks the captured constant keeps its original value
  • Believes a match with no matching case raises an error

context