Pattern Matching with match/case

· 5 min read · Updated March 7, 2026 · intermediate
pattern-matching match-statement control-flow python-3.10

Python 3.10 introduced the match statement, bringing structural pattern matching to the language. If you’ve used switch statements in other languages, the basic syntax will feel familiar—but Python’s version is far more powerful. It can match against data structures, extract values, and apply conditions, all in a single readable block.

This tutorial covers everything you need to transform messy if-elif-else chains into elegant pattern matching code.

Basic Syntax

The match statement compares a subject value against one or more patterns. The first matching case executes, and the rest are ignored.

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the internet"

print(http_error(404))  # Not found

The _ wildcard pattern acts as a catch-all. It never fails to match, making it useful for handling unexpected values.

Matching Multiple Values

Combine several literals in a single pattern using the | operator:

def describe_number(n):
    match n:
        case 0:
            return "Zero"
        case 1 | 2 | 3:
            return "A small number"
        case _ if n < 0:
            return "Negative"
        case _:
            return "A large number"

print(describe_number(2))    # A small number
print(describe_number(-5))    # Negative

This is cleaner than chaining multiple or conditions in an if statement.

Sequence Patterns

Match against lists and tuples by pattern-matching their structure:

def coordinates(point):
    match point:
        case (0, 0):
            return "Origin"
        case (0, y):
            return f"On Y-axis at {y}"
        case (x, 0):
            return f"On X-axis at {x}"
        case (x, y):
            return f"Point at ({x}, {y})"
        case _:
            return "Not a valid point"

print(coordinates((0, 5)))    # On Y-axis at 5
print(coordinates((3, 4)))   # Point at (3, 4)

The variable names in the pattern capture values from the subject. In the (0, y) case, y gets bound to the second element while the first must be zero.

Extended unpacking works too:

def first_two(items):
    match items:
        case [a, b, *rest]:
            return f"First: {a}, Second: {b}, Rest: {rest}"
        case [a]:
            return f"Only one: {a}"
        case []:
            return "Empty list"

print(first_two([1, 2, 3, 4, 5]))
# First: 1, Second: 2, Rest: [3, 4, 5]

Class Patterns

Use class patterns to match against object attributes. This works with any class, including dataclasses:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

def classify_point(p):
    match p:
        case Point(x=0, y=0):
            return "Origin"
        case Point(x=0, y=y):
            return f"On Y-axis"
        case Point(x=x, y=0):
            return f"On X-axis"
        case Point():
            return f"Point at ({p.x}, {p.y})"

print(classify_point(Point(0, 10)))  # On Y-axis
print(classify_point(Point(3, 4)))   # Point at (3, 4)

Python can also use positional arguments if the class defines __match_args__:

class Point:
    __match_args__ = ('x', 'y')
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

# Now this works:
match Point(1, 2):
    case Point(1, y):
        print(f"Y is {y}")  # Y is 2

Mapping Patterns

Match against dictionaries to extract specific keys:

def parse_response(response):
    match response:
        case {"status": 200, "data": data}:
            return f"Success: {data}"
        case {"status": 404, "error": error}:
            return f"Not found: {error}"
        case {"status": status, "error": error}:
            return f"Error {status}: {error}"
        case _:
            return "Unknown response"

print(parse_response({"status": 200, "data": [1, 2, 3]}))
# Success: [1, 2, 3]

Extra keys are ignored, so a dictionary with more fields still matches. Use **rest to capture remaining keys:

def extract_info(user):
    match user:
        case {"name": name, **details}:
            return f"User: {name}, Extra: {details}"

print(extract_info({"name": "Alice", "age": 30, "city": "NYC"}))
# User: Alice, Extra: {'age': 30, 'city': 'NYC'}

Guards

Add conditions to patterns using the if keyword. These “guards” are evaluated after the pattern matches:

def classify_point(p):
    match p:
        case Point(x, y) if x == y:
            return f"Diagonal point at ({x}, {y})"
        case Point(x, y) if x > 0 and y > 0:
            return "First quadrant"
        case Point(x, y) if x < 0 and y < 0:
            return "Third quadrant"
        case Point():
            return "Other quadrant"

print(classify_point(Point(5, 5)))   # Diagonal point at (5, 5)
print(classify_point(Point(3, 1)))   # First quadrant

The capture happens before the guard is evaluated, so you can use the bound variables in your condition.

Matching with Enums

Combine pattern matching with enums for type-safe code:

from enum import Enum

class Color(Enum):
    RED = 'red'
    GREEN = 'green'
    BLUE = 'blue'

def describe_color(color):
    match color:
        case Color.RED:
            return "A warm, bold color"
        case Color.GREEN:
            return "Nature's color"
        case Color.BLUE:
            return "Calm and cool"
        case _:
            return "Unknown color"

print(describe_color(Color.GREEN))  # Nature's color

Note that enum values must be dotted names (like Color.RED) to avoid being interpreted as capture variables.

When to Use match/case

The match statement shines when you’re comparing a single value against multiple conditions, especially when those conditions have structure. API responses, command-line arguments, and parsed data all benefit from pattern matching.

However, keep using if-elif-else when you’re evaluating different expressions or need simple boolean logic. Pattern matching isn’t always the answer—it’s a tool for specific problems.

Common Mistakes

One mistake is forgetting the wildcard case. If your subject might not match any pattern and you don’t have a case _, Python raises a TypeError:

# This will crash if status is not 400, 404, or 418
def bad_http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
    # No wildcard! Will raise TypeError for status=500

Another gotcha: class patterns require parentheses even for no arguments. case Point() works, but case Point does not.

Next Steps

Now you understand how to use Python’s pattern matching for structural data. Practice with your own classes and data structures. The match statement pairs well with dataclasses and TypedDict for building robust parsing logic.

Continue with the next tutorial in the series to deepen your Python skills.