Yesterday you stopped loops with break and skipped items with continue. Today I want to start with Diane's routing function. What does it currently look like?
The one that routes orders to different shipping lanes? It's about forty lines of if/elif. Order type first — standard, express, freight. Then nested conditions inside each branch for weight, destination, hazmat flag. Three elif chains nested three levels deep in some spots.
What happens when Diane adds a new order type?
She adds a branch at the bottom and hopes she didn't miss an interaction with the existing ones. She mentioned she started writing the test first just so she has something to tell her it broke before she ships it.
That test-first habit is a sign the structure has outgrown the tool. if/elif is built for conditions — arbitrary boolean expressions. But what Diane is actually doing isn't checking conditions. She's looking at the shape of the data and routing based on what's there. Python 3.10 added something designed exactly for this.
The match statement? I've seen it mentioned but I've never looked at it.
Structural pattern matching. Think about the sorting conveyor belt at a fulfillment center. Packages come down the belt and a scanner reads the label. Based on size, weight class, destination code — the belt's routing gate flips the package into the right chute. The gate doesn't check conditions in sequence. It reads the whole label and pattern-matches it to a chute in one operation.
So match/case is the gate. It reads the whole order at once instead of checking one field, then another, then a third.
That's exactly the mental model. Here's the simplest form — matching on a value:
status = "express"
match status:
case "express":
print("Priority air lane")
case "freight":
print("Ground freight dock")
case _:
print("Manual review")The underscore — that's a wildcard? Everything else?
The wildcard pattern. It matches anything and it's the only pattern that can never fail. Think of it as the belt's final chute — the one at the end that catches everything the scanner couldn't route.
This looks like switch/case from other languages. I thought Python didn't have switch.
Python didn't until 3.10. But here's where it diverges from switch in C or Java: match doesn't just check equality. It matches structure. Watch what happens when the subject is a dict:
order = {"type": "express", "weight": 3.2}
match order:
case {"type": "express", "weight": w} if w < 5:
print(f"Priority air — small express ({w}kg)")
case {"type": "express", "weight": w}:
print(f"Heavy express — freight tier ({w}kg)")
case {"type": "freight"}:
print("Ground freight dock")
case _:
print("Manual review")Wait — "weight": w — that's not checking if weight equals w. It's capturing the weight into the variable w?
That's the aha. Pattern matching does two things at once: it checks the shape of the data and binds values from inside it into variables you can use immediately. w is a capture variable — the moment the pattern matches, w holds whatever is in the weight field.
So one case line says: "if this dict has a type key equal to "express" and a weight key, capture the weight as w, and if w < 5, run this branch." All of that in one line.
All of that in one line. The if w < 5 part is called a guard — an extra condition that narrows the match after the pattern succeeds. If the guard fails, Python tries the next case.
If the first case fails the guard — say the order is express but weighs 8kg — does Python try the next express case?
Yes. Python tries cases in order, top to bottom. If a pattern matches but the guard fails, it falls through to the next case. The second express case has no guard — so an 8kg express package matches there. You handle the specific subcase first, the general case second.
First case: express, light — priority air. Second case: express, heavy — freight tier. Third: freight — ground dock. Wildcard: anything else. The order of cases is the routing priority.
And that's the crucial difference from elif. With elif, Python evaluates each condition independently — you can have overlapping conditions and the first true one wins by accident. With match, the structure of the data determines which case runs. You're not writing if order["type"] == "express" and order["weight"] < 5 — you're describing the shape you expect.
The conveyor belt analogy again. The scanner recognizes a shape — "this is the express-lightweight label shape" — and fires the right gate.
Unlike Diane's forty lines, when you add a new order type you add one new case block and don't touch the existing ones. Each case is self-contained.
What if the dict has extra keys? Like a customer_id field — does {"type": "express", "weight": w} fail because it's not an exact match?
Dict patterns match subsets. The order only needs to contain the keys the pattern specifies — extra keys are ignored. This is different from tuple or list patterns, which match exactly. A dict pattern says "I care about these fields; I don't care what else is there."
Real order dicts have fifteen fields — customer, address, payment, timestamps. I only care about type and weight for routing. The pattern ignores the noise automatically.
Now let's build the real function. Diane's routing handles five outcomes — priority_air, small_parcel, freight, container_ship, and manual_review:
def route_order(order: dict) -> str:
match order:
case {"type": "express"}:
return "priority_air"
case {"type": "standard", "weight": w} if w < 20:
return "small_parcel"
case {"type": "standard", "weight": w}:
return "freight"
case {"type": "bulk"}:
return "container_ship"
case _:
return "manual_review"Six cases, five routing outcomes, one wildcard. Diane's forty-line function, replaced. And when she adds a new order type she adds one case at the right priority position.
The order of cases is the routing priority — you can read it by reading top to bottom. With the old function, priority was buried in the nesting order. With match, it's explicit.
One thing I want to nail down: the capture variable w — does it exist outside the match block?
Technically yes in Python's current implementation, but treat it as scoped to the case body. Different cases might not bind the same variable, and you'd get a NameError if the wildcard matched. Return or use the value inside the case body. Don't rely on capture variables leaking out.
Same discipline as loop variables after a for loop — technically accessible, but don't rely on it.
Tomorrow: advanced patterns. Sequences — lists and tuples where position matters instead of keys. And class patterns, where the scanner can recognize the type of the object, not just its contents.
A pattern that matches on type and structure at once. I'm already thinking about the parser I tried to write in Track 1. This would have been so much cleaner.
Python 3.10+ structural pattern matching evaluates the shape of a value against case patterns. Unlike switch in C or Java, match does not just check equality — it can destructure dicts, sequences, and class instances, binding parts of the data to capture variables in a single step.
def route_order(order: dict) -> str:
match order:
case {"type": "express"}:
return "priority_air"
case {"type": "standard", "weight": w} if w < 20:
return "small_parcel"
case {"type": "standard", "weight": w}:
return "freight"
case {"type": "bulk"}:
return "container_ship"
case _:
return "manual_review"Pattern types in this lesson:
| Pattern | What it does |
|---|---|
"express" | Literal equality check |
{"type": "express"} | Dict subset check — extra keys OK |
{"weight": w} | Dict subset + capture w into a variable |
case ... if w < 20 | Guard — extra condition after the pattern |
_ | Wildcard — matches anything, cannot fail |
Key semantics:
_ is not a capture variable — it binds nothingPitfall 1: Assuming dict patterns require an exact match. {"type": "express"} matches any dict that contains "type": "express" — extra keys do not prevent the match. This is different from equality: order == {"type": "express"} would be False if order has extra keys.
Pitfall 2: Using capture variables outside the match block. Capture variables like w are technically scoped to the entire function in CPython, but if the wildcard case fires, w was never bound and you get a NameError. Always use capture variables inside the case body only.
Pitfall 3: Match requires Python 3.10+. In older Python the match keyword causes a SyntaxError. Check the environment before adopting structural pattern matching in a shared codebase.
Match supports several more pattern types covered in Day 20:
[first, *rest] matches a list with at least one element, binds first and restPoint(x=x, y=y) matches instances of Point and captures attribute values"express" | "overnight" matches either value{"items": [first, *_]} matches a dict whose items key holds a non-empty listThe PEP 634 motivation was compiler and AST manipulation — match enables recursive descent parsers and state machines that previously required the Visitor pattern and extensive class hierarchies.
Sign up to write and run code in this lesson.
Yesterday you stopped loops with break and skipped items with continue. Today I want to start with Diane's routing function. What does it currently look like?
The one that routes orders to different shipping lanes? It's about forty lines of if/elif. Order type first — standard, express, freight. Then nested conditions inside each branch for weight, destination, hazmat flag. Three elif chains nested three levels deep in some spots.
What happens when Diane adds a new order type?
She adds a branch at the bottom and hopes she didn't miss an interaction with the existing ones. She mentioned she started writing the test first just so she has something to tell her it broke before she ships it.
That test-first habit is a sign the structure has outgrown the tool. if/elif is built for conditions — arbitrary boolean expressions. But what Diane is actually doing isn't checking conditions. She's looking at the shape of the data and routing based on what's there. Python 3.10 added something designed exactly for this.
The match statement? I've seen it mentioned but I've never looked at it.
Structural pattern matching. Think about the sorting conveyor belt at a fulfillment center. Packages come down the belt and a scanner reads the label. Based on size, weight class, destination code — the belt's routing gate flips the package into the right chute. The gate doesn't check conditions in sequence. It reads the whole label and pattern-matches it to a chute in one operation.
So match/case is the gate. It reads the whole order at once instead of checking one field, then another, then a third.
That's exactly the mental model. Here's the simplest form — matching on a value:
status = "express"
match status:
case "express":
print("Priority air lane")
case "freight":
print("Ground freight dock")
case _:
print("Manual review")The underscore — that's a wildcard? Everything else?
The wildcard pattern. It matches anything and it's the only pattern that can never fail. Think of it as the belt's final chute — the one at the end that catches everything the scanner couldn't route.
This looks like switch/case from other languages. I thought Python didn't have switch.
Python didn't until 3.10. But here's where it diverges from switch in C or Java: match doesn't just check equality. It matches structure. Watch what happens when the subject is a dict:
order = {"type": "express", "weight": 3.2}
match order:
case {"type": "express", "weight": w} if w < 5:
print(f"Priority air — small express ({w}kg)")
case {"type": "express", "weight": w}:
print(f"Heavy express — freight tier ({w}kg)")
case {"type": "freight"}:
print("Ground freight dock")
case _:
print("Manual review")Wait — "weight": w — that's not checking if weight equals w. It's capturing the weight into the variable w?
That's the aha. Pattern matching does two things at once: it checks the shape of the data and binds values from inside it into variables you can use immediately. w is a capture variable — the moment the pattern matches, w holds whatever is in the weight field.
So one case line says: "if this dict has a type key equal to "express" and a weight key, capture the weight as w, and if w < 5, run this branch." All of that in one line.
All of that in one line. The if w < 5 part is called a guard — an extra condition that narrows the match after the pattern succeeds. If the guard fails, Python tries the next case.
If the first case fails the guard — say the order is express but weighs 8kg — does Python try the next express case?
Yes. Python tries cases in order, top to bottom. If a pattern matches but the guard fails, it falls through to the next case. The second express case has no guard — so an 8kg express package matches there. You handle the specific subcase first, the general case second.
First case: express, light — priority air. Second case: express, heavy — freight tier. Third: freight — ground dock. Wildcard: anything else. The order of cases is the routing priority.
And that's the crucial difference from elif. With elif, Python evaluates each condition independently — you can have overlapping conditions and the first true one wins by accident. With match, the structure of the data determines which case runs. You're not writing if order["type"] == "express" and order["weight"] < 5 — you're describing the shape you expect.
The conveyor belt analogy again. The scanner recognizes a shape — "this is the express-lightweight label shape" — and fires the right gate.
Unlike Diane's forty lines, when you add a new order type you add one new case block and don't touch the existing ones. Each case is self-contained.
What if the dict has extra keys? Like a customer_id field — does {"type": "express", "weight": w} fail because it's not an exact match?
Dict patterns match subsets. The order only needs to contain the keys the pattern specifies — extra keys are ignored. This is different from tuple or list patterns, which match exactly. A dict pattern says "I care about these fields; I don't care what else is there."
Real order dicts have fifteen fields — customer, address, payment, timestamps. I only care about type and weight for routing. The pattern ignores the noise automatically.
Now let's build the real function. Diane's routing handles five outcomes — priority_air, small_parcel, freight, container_ship, and manual_review:
def route_order(order: dict) -> str:
match order:
case {"type": "express"}:
return "priority_air"
case {"type": "standard", "weight": w} if w < 20:
return "small_parcel"
case {"type": "standard", "weight": w}:
return "freight"
case {"type": "bulk"}:
return "container_ship"
case _:
return "manual_review"Six cases, five routing outcomes, one wildcard. Diane's forty-line function, replaced. And when she adds a new order type she adds one case at the right priority position.
The order of cases is the routing priority — you can read it by reading top to bottom. With the old function, priority was buried in the nesting order. With match, it's explicit.
One thing I want to nail down: the capture variable w — does it exist outside the match block?
Technically yes in Python's current implementation, but treat it as scoped to the case body. Different cases might not bind the same variable, and you'd get a NameError if the wildcard matched. Return or use the value inside the case body. Don't rely on capture variables leaking out.
Same discipline as loop variables after a for loop — technically accessible, but don't rely on it.
Tomorrow: advanced patterns. Sequences — lists and tuples where position matters instead of keys. And class patterns, where the scanner can recognize the type of the object, not just its contents.
A pattern that matches on type and structure at once. I'm already thinking about the parser I tried to write in Track 1. This would have been so much cleaner.
Python 3.10+ structural pattern matching evaluates the shape of a value against case patterns. Unlike switch in C or Java, match does not just check equality — it can destructure dicts, sequences, and class instances, binding parts of the data to capture variables in a single step.
def route_order(order: dict) -> str:
match order:
case {"type": "express"}:
return "priority_air"
case {"type": "standard", "weight": w} if w < 20:
return "small_parcel"
case {"type": "standard", "weight": w}:
return "freight"
case {"type": "bulk"}:
return "container_ship"
case _:
return "manual_review"Pattern types in this lesson:
| Pattern | What it does |
|---|---|
"express" | Literal equality check |
{"type": "express"} | Dict subset check — extra keys OK |
{"weight": w} | Dict subset + capture w into a variable |
case ... if w < 20 | Guard — extra condition after the pattern |
_ | Wildcard — matches anything, cannot fail |
Key semantics:
_ is not a capture variable — it binds nothingPitfall 1: Assuming dict patterns require an exact match. {"type": "express"} matches any dict that contains "type": "express" — extra keys do not prevent the match. This is different from equality: order == {"type": "express"} would be False if order has extra keys.
Pitfall 2: Using capture variables outside the match block. Capture variables like w are technically scoped to the entire function in CPython, but if the wildcard case fires, w was never bound and you get a NameError. Always use capture variables inside the case body only.
Pitfall 3: Match requires Python 3.10+. In older Python the match keyword causes a SyntaxError. Check the environment before adopting structural pattern matching in a shared codebase.
Match supports several more pattern types covered in Day 20:
[first, *rest] matches a list with at least one element, binds first and restPoint(x=x, y=y) matches instances of Point and captures attribute values"express" | "overnight" matches either value{"items": [first, *_]} matches a dict whose items key holds a non-empty listThe PEP 634 motivation was compiler and AST manipulation — match enables recursive descent parsers and state machines that previously required the Visitor pattern and extensive class hierarchies.