Match Statements

The match statement, added in Python 3.10, compares a value against a series of patterns. It can do what a switch does in other languages, but it goes much further: it can check the shape of lists, tuples, dictionaries and objects, and pull values out of them at the same time.

Matching literal values

PY literal.py
def http_status(code):
    match code:
        case 200:
            return "OK"
        case 301 | 302:
            return "Redirect"
        case 404:
            return "Not Found"
        case 500:
            return "Server Error"
        case _:
            return "Unknown"

for c in [200, 302, 404, 418]:
    print(c, http_status(c))
Output
200 OK
302 Redirect
404 Not Found
418 Unknown

Cases are tried in order and the first match wins — there's no fall-through, so no break is needed. | combines alternatives, and _ is the wildcard that matches anything.

Capturing values

A bare name in a pattern doesn't compare — it captures whatever is in that position:

PY capture.py
def describe(point):
    match point:
        case (0, 0):
            return "origin"
        case (0, y):
            return f"on the y-axis at {y}"
        case (x, 0):
            return f"on the x-axis at {x}"
        case (x, y):
            return f"at ({x}, {y})"

for p in [(0, 0), (0, 5), (3, 0), (2, 7)]:
    print(describe(p))
Output
origin
on the y-axis at 5
on the x-axis at 3
at (2, 7)

Sequence patterns

Patterns can check length and capture the rest with * — perfect for parsing commands:

PY commands.py
def run(command):
    match command.split():
        case ["quit" | "exit"]:
            return "Goodbye"
        case ["go", direction]:
            return f"Moving {direction}"
        case ["take", *items] if items:
            return f"Taking {', '.join(items)}"
        case ["take"]:
            return "Take what?"
        case _:
            return f"Unknown command: {command!r}"

for c in ["go north", "take lamp key", "take", "exit", "dance"]:
    print(run(c))
Output
Moving north
Taking lamp, key
Take what?
Goodbye
Unknown command: 'dance'

Guards

An if after a pattern adds an extra condition — the case only matches if both the pattern and the guard succeed:

PY guards.py
def classify(n):
    match n:
        case 0:
            return "zero"
        case int() if n < 0:
            return "negative int"
        case int() if n % 2 == 0:
            return "even int"
        case int():
            return "odd int"
        case float():
            return "float"
        case _:
            return "not a number"

for v in [0, -3, 8, 7, 2.5, "7"]:
    print(repr(v), "->", classify(v))
Output
0 -> zero
-3 -> negative int
8 -> even int
7 -> odd int
2.5 -> float
'7' -> not a number

int() in a pattern is a class pattern: it matches any instance of int.

Mapping patterns

Dictionary patterns check for keys — very handy for JSON-like data. Extra keys are ignored:

PY mapping.py
def handle(event):
    match event:
        case {"type": "click", "x": x, "y": y}:
            return f"click at {x},{y}"
        case {"type": "key", "key": "Enter" | "Return"}:
            return "submit"
        case {"type": "key", "key": key}:
            return f"pressed {key}"
        case {"type": str(kind)}:
            return f"unhandled {kind} event"
        case _:
            return "invalid event"

events = [
    {"type": "click", "x": 10, "y": 20, "button": 1},
    {"type": "key", "key": "Enter"},
    {"type": "key", "key": "a"},
    {"type": "scroll"},
    {"oops": True},
]
for e in events:
    print(handle(e))
Output
click at 10,20
submit
pressed a
unhandled scroll event
invalid event

Class patterns

PY classes.py
from dataclasses import dataclass

@dataclass
class Circle:
    radius: float

@dataclass
class Rect:
    width: float
    height: float

def area(shape):
    match shape:
        case Circle(radius=r):
            return 3.14159 * r ** 2
        case Rect(width=w, height=h) if w == h:
            return w * w
        case Rect(width=w, height=h):
            return w * h

print(area(Circle(1)), area(Rect(3, 3)), area(Rect(2, 5)))
Output
3.14159 9 10

Dataclasses (lesson 40) work naturally with class patterns.

A common gotcha: constants

Because bare names capture, you can't match against a variable directly:

PY gotcha.py
QUIT = "q"

def check(key):
    match key:
        case "q":
            return "quit (literal works)"
        case _:
            return "other"

class Keys:
    QUIT = "q"

def check_dotted(key):
    match key:
        case Keys.QUIT:
            return "quit (dotted name works)"
        case _:
            return "other"

print(check("q"), "|", check_dotted("q"))
Output
quit (literal works) | quit (dotted name works)

Writing case QUIT: would capture any value into a new variable named QUIT. Use literals, or dotted names like Keys.QUIT or an enum member (Color.RED), which are compared rather than captured.

When to use match

Use match when you're branching on the structure of data — command parsing, message handling, tree processing. For a handful of simple comparisons, if/elif or a dictionary lookup is often simpler.

Try it yourself

Try it yourself
Console output