Structural Pattern Matching (PEP 634, Python 3.10+) is the single biggest change to Python's control flow since generators. It's not a switch statement — it's a way to branch on the shape of data. You hand it a value, and it destructures it against a series of patterns, binding variables along the way.
If you've ever written a chain of isinstance() checks, nested if/elif blocks testing dictionary keys, or manual tuple unpacking to dispatch on data shape, pattern matching replaces all of that with something the reader can parse in one pass. It shines hardest in code that routes on data: HTTP handlers, event dispatchers, protocol parsers, AST walkers, anything where the logic is "look at what this thing is, then act accordingly."
Master python pattern matching
Take the Python Fundamentals course with hands-on lessons and challenges.
Each case matches a Request dataclass by its attributes. OR patterns collapse GET|HEAD into one branch. Guard clauses validate the path prefix and body presence. The final case captures an unknown method for a clean 405 response.
Mapping patterns match on dictionary keys and destructure nested values in one expression. The sequence pattern [*items] captures the list inside the nested dict. Cases are tried top to bottom, so the more specific match (non-empty items) is checked before the empty-list fallback.
Sequence patterns destructure the token list by position and length simultaneously. The star pattern (*flags) captures remaining arguments. A guard clause restricts valid environments, while the next case catches the invalid-environment path. This replaces argparse for simple internal CLI tools.
Class patterns match dataclass instances by type and attribute values at once. The recursive evaluate function handles each node type cleanly. Matching on op="+" inside BinOp narrows both the type and the operation in a single pattern. This is the canonical use case for pattern matching — tree-walking interpreters, compilers, and linters.
Guard clauses let you add conditions that depend on runtime values like the current time or amount thresholds. The stale-login check combines a class pattern with a timestamp comparison. Notice how captured variables (uid, ip, amt) are immediately available in the case body without any extra unpacking.
Nested mapping patterns validate configuration structure and types in a single expression. Using str(host) and int(port) inside patterns checks the type and captures the value simultaneously. The wildcard _ at the end is the catch-all. This approach replaces deeply nested key-existence checks and isinstance calls when validating config dicts.
Using a bare name where you meant a literal — `case status_code:` captures any value into `status_code` instead of matching the variable's value
Bare names are always capture patterns. To match a constant, use a dotted name (`case HTTPStatus.OK:`), a literal (`case 200:`), or a guard clause (`case code if code == expected_status:`). This is the single most common source of bugs with match/case.
Putting a catch-all `case _:` or a broad capture pattern before more specific cases — later cases become unreachable dead code
Order cases from most specific to least specific. Python tries patterns top to bottom and takes the first match. Put guarded patterns before unguarded ones for the same structure, and put the wildcard last.
Expecting mapping patterns to require an exact match on all keys — `case {"status": 200}:` matches any dict that *contains* the key `"status"` with value 200, even if it has 50 other keys
Mapping patterns match on key presence, not exact shape. This is by design — it lets you match on the keys you care about. If you need to reject extra keys, add a guard: `case {"status": 200, **rest} if not rest:`.
Forgetting that sequence patterns distinguish lists from tuples — `case [x, y]:` matches a list but not a tuple, and `case (x, y):` is grouping, not tuple matching
Use `case [x, y]:` for both lists and tuples in practice — Python's sequence pattern matches any sequence type. But `case (x, y):` is treated as a grouping expression, not a tuple pattern. For explicit tuple matching, use `case tuple((x, y)):`.
Structural Pattern Matching (match/case, Python 3.10+) lets you branch on the shape of data rather than testing values one condition at a time. Literal patterns match exact values, sequence patterns destructure lists, mapping patterns extract dictionary values by key, and class patterns match object attributes — all in a single expression that binds variables automatically. Guard clauses handle runtime conditions that pure structure can't express. Use it for request routing, event dispatching, config validation, AST walking, and anywhere you're currently writing isinstance/elif chains. Always order cases from most specific to least specific, and always end with a wildcard.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.