Structural Pattern Matching with match/case

· 3 min read · Updated March 12, 2026 · intermediate
python pattern-matching control-flow

Python 3.10 introduced a powerful new feature: structural pattern matching, commonly known as the match/case statement. If you’ve used switch statements in other languages, you might think you already know what this is. But Python’s implementation goes far beyond simple value switching—it lets you match against data structures, extract values, and handle complex conditional logic with clean, readable syntax.

Why Use match/case?

Before the match statement, you’d handle complex conditional logic with a chain of if/elif/else statements or a dictionary dispatch pattern. Both work, but they become unwieldy as conditions grow. The match statement lets you express intent directly: “match this value against these patterns.”

Consider a function that processes HTTP status codes:

def handle_response(status):
    match status:
        case 400:
            return "Bad request"
        case 401:
            return "Unauthorized"
        case 403:
            return "Forbidden"
        case 404:
            return "Not found"
        case 500:
            return "Internal server error"
        case _:
            return f"Unknown status code: {status}"

The _ wildcard pattern matches anything—it’s like the final else in an if chain.

Literal Patterns

The simplest form of matching uses literal values. Strings, numbers, and booleans all work as patterns:

def describe_number(n):
    match n:
        case 0:
            return "Zero"
        case 1:
            return "One"
        case 2:
            return "Two"
        case _:
            return f"Something else: {n}"

You can also match multiple literals with the | operator (the “or pattern”):

def http_method(method):
    match method.upper():
        case "GET" | "HEAD" | "OPTIONS":
            return "Safe method"
        case "POST" | "PUT" | "PATCH":
            return "Unsafe method"
        case _:
            return "Unknown method"

Capturing Patterns

Sometimes you want to capture the matched value for use in the case body. Prefix a pattern with a variable name to capture it:

def process(data):
    match data:
        case [x, y]:
            return f"Got a two-element list: {x}, {y}"
        case {"name": name, "age": age}:
            return f"Got a dict with name={name}, age={age}"
        case _:
            return "Unknown structure"

The variable name (without quotes) captures whatever matches that position.

Class Patterns

One of the most powerful features is matching against class structures. You can match dataclasses, namedtuples, and regular classes with positional or keyword arguments:

from dataclasses import dataclass
from datetime import datetime

@dataclass
class Event:
    timestamp: datetime
    source: str

@dataclass  
class ClickEvent(Event):
    element_id: str

@dataclass
class PageViewEvent(Event):
    path: str
    duration_seconds: int

def process_event(event):
    match event:
        case ClickEvent(timestamp, _, element_id):
            return f"Click on {element_id} at {timestamp}"
        case PageViewEvent(timestamp, _, path, duration):
            return f"Viewed {path} for {duration}s"
        case _:
            return "Unknown event type"

Note how we use _ to ignore values we don’t need—in this case, the source field.

Pattern Guards

Sometimes you need an extra condition beyond the pattern itself. That’s what guards are for—if clauses after your pattern:

def classify_point(point):
    match point:
        case (0, 0):
            return "Origin"
        case (x, 0) if x > 0:
            return f"Positive x-axis: {x}"
        case (0, y) if y > 0:
            return f"Positive y-axis: {y}"
        case (x, y):
            return f"Point at ({x}, {y})"

The guard (if x > 0) is only checked if the pattern matches.

Matching with AS

The as pattern lets you both match a pattern and capture the entire match:

def handle_response(response):
    match response:
        case {"status": 200, "data": data} as success:
            return f"Success with data: {data}"
        case {"error": error_msg} as error:
            return f"Error: {error_msg}"
        case _:
            return "Unexpected response format"

When to Use match/case

The match statement shines when you’re:

  • Processing structured data (JSON, dataclasses, namedtuples)
  • Implementing state machines
  • Handling protocol messages or command parsing
  • Replacing complex if/elif chains that compare against multiple values

For simple value comparisons, a dictionary dispatch or simple if/else might still be clearer. The match statement earns its keep when patterns become complex.

See Also