Pattern Matching with match-case

Concepts covered: Pattern Matching

Python 3.10 introduced match-case, also known as structural pattern matching. It provides a cleaner way to handle multiple conditions compared to long if-elif-else chains. The match statement compares a value against several patterns and executes the code for the first matching pattern.

Basic match-case syntax

The match keyword is followed by the value to match, then case clauses define patterns to match against:

def get_day_type(day):
    match day:
        case "Saturday" | "Sunday":
            return "weekend"
        case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday":
            return "weekday"
        case _:
            return "unknown"

print(get_day_type("Saturday"))
print(get_day_type("Wednesday"))
print(get_day_type("Holiday"))

Output:

weekend
weekday
unknown

The | operator matches multiple patterns (like "or"). The _ pattern is a wildcard that matches anything, like a default case.

Matching Values

Match-case is excellent for handling discrete values. Status codes and commands are the usual examples, and types work the same way:

def handle_http_status(code):
    match code:
        case 200:
            return "OK"
        case 201:
            return "Created"
        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 "Unknown Status"

print(handle_http_status(200))
print(handle_http_status(404))
print(handle_http_status(418))

Output:

OK
Not Found
Unknown Status

Matching with guards

You can add an if clause after a pattern to add a further condition. This is called a guard:

def categorize_number(n):
    match n:
        case 0:
            return "zero"
        case n if n < 0:
            return "negative"
        case n if n % 2 == 0:
            return "positive even"
        case _:
            return "positive odd"

print(categorize_number(0))
print(categorize_number(-5))
print(categorize_number(8))
print(categorize_number(7))

Output:

zero
negative
positive even
positive odd

Guards let you add conditions that go beyond simple value matching. The pattern variable (n in this case) captures the matched value for use in the guard and the block.

Match-case supports several pattern types, each suited to a different kind of match:

Matching Sequences

Match-case can destructure a list or tuple and check its shape and its contents in one pattern. This is powerful for parsing data structures where the shape of the data determines how to handle it. Pattern matching automatically extracts values from the matched structure and binds them to variables:

def describe_point(point):
    match point:
        case (0, 0):
            return "origin"
        case (x, 0):
            return "on x-axis at " + str(x)
        case (0, y):
            return "on y-axis at " + str(y)
        case (x, y):
            return "point at (" + str(x) + ", " + str(y) + ")"
        case _:
            return "not a point"

print(describe_point((0, 0)))
print(describe_point((5, 0)))
print(describe_point((0, 3)))
print(describe_point((2, 4)))

Output:

origin
on x-axis at 5
on y-axis at 3
point at (2, 4)
Use match-case when
  • Many discrete values to handle
  • Pattern matching on structure
  • Command/event dispatching
  • Cleaner than long elif chains
Use if-elif when
  • Complex boolean conditions
  • Only 2-3 conditions
  • Range-based comparisons
  • Python version < 3.10

The wildcard pattern _ is the match-case equivalent of the else clause. Always include it to handle unexpected values gracefully rather than silently returning None when no case matches.

The | operator in match-case lets you group values that share the same handling. This is cleaner than a long elif chain with many == comparisons when several inputs lead to the same outcome.

Match-case was introduced in Python 3.10, so it is not available in older environments. Data pipelines still running on legacy infrastructure need if-elif chains instead.

Match-case reads naturally when each case corresponds to a meaningful category. The structure forces you to be explicit about every value the code handles, which makes exhaustive handling of a set of inputs clearer than a scattered chain of elif statements.

Guard clauses inside match cases (using "if" after the pattern) let you add conditions that go beyond simple value equality. This keeps complex logic organized inside a single match block rather than spreading it across nested if statements.

A long elif chain that compares one variable against many literal values is usually a candidate for match-case, which names the variable once instead of repeating it in every branch.

About this lesson

This section is part of the Control Flow: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.