match-case statement

RoadmapsPython

Overview

`match-case` (Python 3.10+) provides structural pattern matching. It compares a subject against the STRUCTURE of each `case`, not just its equality. A capture pattern binds a name directly from inside the matched structure: def route(event): match event: case {'type': 'login', 'user': user}: return f'login by {user}' case {'type': 'logout'}: return 'logout' case _: return 'unknown' The first `case` matches any `dict` containing both a `'type'` key equal to `'login'` and a `'user'` key. The value under `'user'` is captured into the variable `user`. The wildcard `_` is the catch-all — it matches anything and binds nothing. Like `if`/`elif`, only the first matching `case` runs. You can also OR literal values using the `|` operator, like `case 'GET' | 'HEAD':` to match either string. A major trap is that a bare variable name in a `case` always captures the subject; it never compares against an existing variable: STATUS_OK = 200 match response_code: case STATUS_OK: # TRAP! Captures response_code into new variable STATUS_OK pass case code if code == STATUS_OK: # CORRECT: Uses a guard pass To match against an existing variable's value, you must use a guard (`if`) or use a dotted name (like `http.HTTPStatus.OK`). Another sharp edge is that sequence patterns require an exact length match by default. If your list has extra items, the match will fail: event = ['drop', 'table', 'users', 'cascade'] match event: case ['drop', 'table']: pass # Fails: sequence length must match exactly case ['drop', 'table', *rest]: pass # Works: *rest captures remaining items To match a prefix and ignore or capture the rest, you must use a starred expression like `*rest`.

`match-case` replaces cascading `isinstance` checks and multi-key `dict` lookups when dispatching on message types or API events. Without it, routing a dozen event types requires a long `if`/`elif` chain full of repeated `dict.get()` calls.

Where used: JSON webhook event routing, CLI subcommand dispatch, protocol message type handlers

Why learn this

Code walkthrough

def handle(event):
    match event:
        case {'type': 'signup', 'email': email}:
            return f'new user: {email}'
        case {'type': 'logout'}:
            return 'logged out'
        case _:
            return 'unknown'

print(handle({'type': 'signup', 'email': 'a@b.com'}))
print(handle({'type': 'logout'}))
print(handle({'type': 'ping'}))

Focus: The first `case` matches the dict's structure AND binds the `'email'` value to a variable; the second matches on type alone; `_` catches anything unmatched.

Common mistakes

Glossary

structural pattern matching
Checking data against a specific layout or structure, rather than just comparing simple values. For example, `case {'type': 'login', 'user': u}:` matches any dict with those two keys.
wildcard
A special symbol (like `_`) that acts as a catch-all, matching anything that hasn't been specifically caught by other rules. For example, `case _: return 'unknown'`.

Recall questions

Understanding checks

What does this print, and why?

`A`

Two rules combine: the first matching `case` wins, AND a mapping pattern matches any `dict` that CONTAINS the given keys (extra keys are allowed). So `{'type': 'login'}` already matches the event, and the broader `case` fires first — the more specific `case` is never reached. Most people guess `B`.

What does this print?

`done`

No `case` matches `500` and there is no wildcard `case _`, so the `match` statement simply does nothing — it is not an error. Execution continues to the `print` after the `match`.

Practice tasks

HTTP method dispatcher

Modify the given `dispatch(method)` function. Replace the `pass` statement with a `match`-`case` block that returns `'read'` for `'GET'`, `'write'` for `'POST'` or `'PUT'`, `'delete'` for `'DELETE'`, and `'unsupported'` for anything else.

Challenge

API event router

Write `route_event(event)` that handles `dict` payloads. If `type='db_query'` and a `'table'` key is present, return `'querying <table>'`. If `type='cache_clear'`, return `'clearing cache'`. If `type='healthcheck'`, return `'ok'`. For any other type, return `'unhandled: <type>'`. Test all four patterns.

Continue learning

Previous: if/elif/else and Python truthiness

Previous: Dictionaries: operations and use cases

Return to Python Roadmap