match

Not just a switch: patterns test shape and type and pull values out in one step. The trap — a bare name in a case captures anything instead of comparing against a constant.

Control flowPython 3.10+Live demo
literals, | and _
match status:
    case 200 | 201:
        ok()
    case _:
        other()
sequence + guard
match cmd.split():
    case ["go", where]:
        go(where)
    case [op, *args] if args:
        run(op, args)
mapping
match event:
    case {"type": "key", "k": k}:
        press(k)
class
match shape:
    case Point(x=0, y=y):
        on_y_axis(y)
    case int(n) | float(n):
        number(n)
Use for
dispatch on the shape of data: commands, parsed JSON, AST nodes, events
Result
a statement — runs the first matching case, or nothing if none match
Pairs with
case, _, | (or-patterns), if guards, as, dataclasses
Watch out
case NAME: captures anything — constants need a dot (Color.RED)

Demo

Live evaluation
Map an HTTP status code to text. | tries several literals, _ catches everything else.
Try:
Inputs
statusinta status code
Code
status = 201
match status:
    case 200 | 201:
        text = "OK"
    case 404:
        text = "Not Found"
    case 500 | 502 | 503:
        text = "Server error"
    case _:
        text = "Unknown"
text
Result
'OK'

In the sequence tab, "go north now" is unknown: ["go", direction] needs exactly two words. "take" alone matches the shape ["take", *items] with items == [], but the guard if items is false, so matching moves on. In the capture trap tab, "blue" still prints matched RED and RED itself is now "blue" — the case bound the name instead of comparing. The dotted constant tab is the fix; with more cases after a bare name, Python refuses to compile at all (see the pitfalls).

Syntax slots

NameTypeRequiredDescription
subjectexpressionyesEvaluated once. A comma-separated list without brackets is a tuple: match x, y:.
patternname / patternyesLiteral, capture name, _ wildcard, dotted constant, [sequence], {mapping}, Class(...), p1 | p2, or p as name.
guardexpressionnoif condition after the pattern. Checked after the pattern matched and bound its names; false → try the next case.
bodyblockyesRuns for the first case that matches. No fall-through, no break needed.

Common patterns

Dispatch on parsed JSON
Mapping patterns check keys and types at once; extra keys are ignored.
def handle(msg):
    match msg:
        case {"type": "join", "user": str(name)}:
            return f"{name} joined"
        case {"type": "say", "user": str(name), "text": text}:
            return f"{name}: {text}"
        case _:
            raise ValueError(f"bad message: {msg!r}")
Dataclasses as class patterns
Keyword sub-patterns read attributes; positional ones use __match_args__, which @dataclass generates.
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

def where(p):
    match p:
        case Point(x=0, y=0):
            return "origin"
        case Point(x=0, y=y):
            return f"y-axis at {y}"
        case Point(x, y):
            return f"({x}, {y})"
Enum members are dotted names
Color.RED is a value pattern, so enums work as case constants directly.
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2

def hex_of(c):
    match c:
        case Color.RED:
            return "#f00"
        case Color.GREEN:
            return "#0f0"
Type dispatch with capture
int(n), str(s): built-in classes take one positional sub-pattern that matches the whole value.
def describe(v):
    match v:
        case bool(b):
            return f"flag {b}"
        case int(n) | float(n):
            return f"number {n}"
        case str() as s:
            return f"text {s!r}"
        case _:
            return "other"

Examples

1. Literals with | and a wildcard
x = 3 match x: case 1 | 2 | 3: print('small') case _: print('big')
Returns
small
2. Sequence pattern with a star
match [1, 2, 3, 4]: case [first, *rest]: print(first, rest)
Returns
1 [2, 3, 4]
3. Mapping patterns ignore extra keys
event = {'type': 'click', 'x': 3, 'extra': True} match event: case {'type': 'click', 'x': x}: print('click at', x)
Returns
click at 3
4. Guard with if
point = (3, 3) match point: case (x, y) if x == y: print('diagonal', x) case (x, y): print('other')
Returns
diagonal 3
5. Class pattern with as
match 'hi': case int() as n: print('int', n) case str() as s: print('str', s)
Returns
str hi
6. No case matches → nothing happens
match 7: case 1: print('one') print('after match')
Returns
after match
7. match, case and _ are soft keywords
import keyword keyword.softkwlist
Returns
['_', 'case', 'match', 'type']
8. So they still work as names
match = [1, 2] case = len(match) _ = case * 10 (match, case, _)
Returns
([1, 2], 2, 20)

Pitfalls

1. case NAME: captures instead of comparing
A bare name is a capture pattern: it matches anything and binds it. Followed by other cases, Python rejects the code. Use a dotted name (class attribute, enum member, module constant) or a literal.
bare constant
compile('match c:\n    case RED:\n        pass\n    case _:\n        pass', '<demo>', 'exec')
SyntaxError: name capture 'RED' makes remaining patterns unreachable
dotted name
class Color:
    RED = 'red'
c = 'blue'
match c:
    case Color.RED:
        print('red')
    case _:
        print('not red')
not red
2. case str: instead of case str():
Without parentheses, str is just another capture name — it rebinds str. A class pattern needs the call syntax.
bare class name
compile('match v:\n    case str:\n        pass\n    case int():\n        pass', '<demo>', 'exec')
SyntaxError: name capture 'str' makes remaining patterns unreachable
class pattern
v = 42
match v:
    case str():
        print('text')
    case int():
        print('int')
int
3. Expecting a string to match a sequence pattern
str, bytes and bytearray are deliberately NOT treated as sequences, so [a, b] never matches "ab". Convert explicitly if you want characters.
string subject
match 'ab':
    case [a, b]:
        print(a, b)
    case _:
        print('no match')
no match
list(subject)
match list('ab'):
    case [a, b]:
        print(a, b)
a b
4. case 1 also matches True (and 1.0)
Number literals compare with ==, and True == 1. The literals True, False and None compare with is — so put bool cases first, or use a class pattern.
int literal first
match True:
    case 1:
        print('one')
    case True:
        print('true')
one
bool first
match True:
    case True:
        print('true')
    case 1:
        print('one')
true

When to use

Use it
  • Branching on the structure of data — lists of a given length, dicts with certain keys, objects of a type
  • Command / message / token dispatch where each branch also unpacks values
  • Replacing isinstance + indexing + len checks with one readable pattern
Reach for something else
  • Code that must run on Python 3.9 or earlier
  • A simple value → value table → a dict lookup
  • One or two plain comparisons → if / elif

Notes

CPython impl
Patterns compile to ordinary tests (isinstance, len, key lookups, ==), tried case by case from the top — like an if / elif chain, not a jump table
Soft keywords
match, case and _ are keywords only in match statement positions, so older code using them as names keeps working (keyword.softkwlist, added in 3.9)
Binding
Names bound by a pattern stay bound after the match, like any assignment — even from a case whose guard then failed

FAQ

Since 3.10 it has match / case, which covers switch and much more. Cases never fall through, so no break is needed; _ plays the role of default. On older versions use if / elif or a dict lookup.

History

3.10
The match statement added (PEP 634, structural pattern matching)