Structural Pattern Matching in Python — Core Concepts

Why structural pattern matching matters

Python 3.10 introduced match/case statements (PEPs 634–636), bringing a feature common in languages like Rust, Scala, and Haskell. It’s more than a switch statement — it inspects the structure of data, not just equality.

This shines when processing JSON APIs, command parsers, AST nodes, or any situation where data comes in multiple shapes and you need to handle each one differently.

The six pattern types

1. Literal patterns

Match exact values — numbers, strings, booleans, None:

match status_code:
    case 200:
        print("OK")
    case 404:
        print("Not found")
    case _:
        print(f"Other: {status_code}")

The _ wildcard matches anything and acts as the default case.

2. Capture patterns

A bare name captures the matched value into a variable:

match command:
    case "quit":
        exit()
    case other:
        print(f"Unknown command: {other}")

Here other captures whatever didn’t match "quit". This is why you can’t use variable names as patterns directly — Python treats them as captures, not lookups.

3. Sequence patterns

Match lists or tuples by structure:

match point:
    case [x, y]:
        print(f"2D point at ({x}, {y})")
    case [x, y, z]:
        print(f"3D point at ({x}, {y}, {z})")
    case [first, *rest]:
        print(f"Starts with {first}, then {len(rest)} more")

The *rest syntax captures remaining elements, just like unpacking in assignments.

4. Mapping patterns

Match dictionaries by key:

match event:
    case {"type": "click", "x": x, "y": y}:
        handle_click(x, y)
    case {"type": "keypress", "key": key}:
        handle_key(key)

Extra keys in the dictionary are ignored — the pattern only checks the keys you specify. This makes it forgiving when APIs add new fields.

5. Class patterns

Match objects by type and attributes:

match shape:
    case Circle(radius=r) if r > 0:
        area = 3.14159 * r ** 2
    case Rectangle(width=w, height=h):
        area = w * h

The if r > 0 is a guard — an extra condition that must be true for the case to match.

6. OR patterns

Combine patterns with |:

match command:
    case "quit" | "exit" | "q":
        shutdown()
    case "help" | "?":
        show_help()

How it works

When Python encounters a match statement, it evaluates the subject once, then tests each case pattern in order. The first matching pattern wins. If no pattern matches and there’s no wildcard _ case, execution continues after the match block — no error is raised.

Pattern matching does not use __eq__. For class patterns, it checks isinstance and then accesses attributes. For sequences, it checks length and element patterns. This distinction matters — custom __eq__ methods won’t affect pattern matching behaviour.

Common misconception

“match/case is just a switch statement.”

Switch statements (like C’s) compare a value against constants. Python’s match inspects structure — it can destructure nested lists, unpack object attributes, and combine type checking with value extraction in a single expression. It’s closer to Rust’s match than C’s switch.

When to use it

Pattern matching excels when:

  • Parsing structured data — JSON from APIs, configuration files, protocol messages
  • Command dispatching — CLI tools, chatbot commands, event handlers
  • AST processing — Walking syntax trees in compilers, linters, or code generators
  • State machines — Each state transition matches a different event shape

Avoid it for simple value comparisons where if/elif is clearer, or when you have only two branches.

Practical tips

  • Always include a wildcard case (case _:) to handle unexpected inputs gracefully.
  • Put specific patterns before general ones — Python checks top to bottom and stops at the first match.
  • Use guards sparingly — if a guard gets complex, consider extracting the logic into a function.
  • Remember: bare names capture. To match a constant, use case MyEnum.VALUE: or a literal, not case my_variable:.

The one thing to remember: Python’s match/case checks the shape of your data — type, structure, and content — in one clean expression, replacing tangled if/elif chains with readable, destructuring patterns.

pythonfundamentalspython310

See Also

  • Python 310 New Features Python 3.10 gave programmers a shape-sorting machine, friendlier error messages, and cleaner ways to say 'this or that' in type hints.
  • Python 311 New Features Python 3.11 made everything faster, error messages smarter, and let you catch several mistakes at once instead of stopping at the first one.
  • Python 312 New Features Python 3.12 made type hints shorter, f-strings more powerful, and started preparing Python's engine for a world without the GIL.
  • Python 313 New Features Python 3.13 finally lets multiple tasks run at the same time for real, added a speed booster engine, and gave the interactive prompt a colourful makeover.
  • Python Exception Groups Python's ExceptionGroup is like getting one report card that lists every mistake at once instead of stopping at the first one.