|$ curl https://forge-ai.dev/api/markdown?path=docs/python/match-case
$cat docs/match-/-case.md
updated Today·18-24 min read·published

match / case

PythonmatchPEP 634Intermediate🎯Free Tools
Introduction

Structural pattern matching (match/case, PEP 634) destructures data by shape — not just equality switches. Available in Python 3.10+.

info

Patterns can bind names. A bare name like case x: matches anything and binds x — put it last.
Literal & Or Patterns
literals.py
Python
1def handle(code: int) -> str:
2 match code:
3 case 200 | 201 | 204:
4 return "ok"
5 case 301 | 302:
6 return "redirect"
7 case 400 | 404 | 422:
8 return "client"
9 case 500:
10 return "server"
11 case _:
12 return "other"
13
14print(handle(201), handle(404), handle(418))
Guards
guards.py
Python
1def classify(n: int) -> str:
2 match n:
3 case x if x < 0:
4 return "negative"
5 case 0:
6 return "zero"
7 case x if x % 2 == 0:
8 return "even"
9 case _:
10 return "odd"
11
12print([classify(i) for i in (-1, 0, 2, 3)])
Sequence Patterns
sequence.py
Python
1def parse(cmd: list[str]) -> str:
2 match cmd:
3 case ["quit"]:
4 return "bye"
5 case ["load", filename]:
6 return f"load {filename}"
7 case ["move", x, y]:
8 return f"move to {x},{y}"
9 case ["go", *rest] if rest:
10 return "go " + " ".join(rest)
11 case [head, *mid, tail]:
12 return f"{head}..{tail} ({len(mid)} mid)"
13 case _:
14 return "unknown"
15
16print(parse(["load", "a.txt"]))
17print(parse(["go", "north", "fast"]))
18print(parse(["a", "b", "c", "d"]))
Mapping Patterns
mapping.py
Python
1def handle_event(event: dict) -> str:
2 match event:
3 case {"type": "click", "x": int(x), "y": int(y)}:
4 return f"click {x},{y}"
5 case {"type": "key", "key": str(k)} if len(k) == 1:
6 return f"char {k}"
7 case {"type": "key", "key": k}:
8 return f"key {k}"
9 case {"type": t, **rest}:
10 return f"other {t} keys={list(rest)}"
11 case _:
12 return "invalid"
13
14print(handle_event({"type": "click", "x": 1, "y": 2}))
15print(handle_event({"type": "key", "key": "Enter"}))
📝

note

Mapping patterns ignore extra keys unless you capture them with **rest.
Class Patterns
class_patterns.py
Python
1from dataclasses import dataclass
2
3@dataclass
4class Point:
5 x: int
6 y: int
7
8@dataclass
9class Circle:
10 center: Point
11 radius: int
12
13def describe(shape) -> str:
14 match shape:
15 case Point(x=0, y=0):
16 return "origin"
17 case Point(x, y) if x == y:
18 return f"diagonal {x}"
19 case Point(x, y):
20 return f"point {x},{y}"
21 case Circle(center=Point(0, 0), radius=r):
22 return f"unit-centered r={r}"
23 case Circle(center=Point(x, y), radius=r):
24 return f"circle at {x},{y} r={r}"
25 case _:
26 return "unknown"
27
28print(describe(Point(0, 0)))
29print(describe(Circle(Point(1, 2), 5)))

Class patterns use __match_args__ for positional capture. Dataclasses set this automatically.

match_args.py
Python
1class Pair:
2 __match_args__ = ("left", "right")
3 def __init__(self, left, right):
4 self.left = left
5 self.right = right
6
7def mid(p):
8 match p:
9 case Pair(a, b):
10 return (a + b) / 2
11
12print(mid(Pair(2, 4)))
as Patterns & Capture
as_pat.py
Python
1def normalize(value):
2 match value:
3 case {"error": msg as m}:
4 return f"ERR:{m}"
5 case [x, y] as pair:
6 return ("pair", pair, x + y)
7 case str() as s if s.strip():
8 return s.strip()
9 case _:
10 return None
11
12print(normalize({"error": "boom"}))
13print(normalize([1, 2]))
14print(normalize(" hi "))
match vs if/elif
Use match whenUse if/elif when
Destructuring nested dataSimple boolean conditions
Multiple shapes / ADTsRange checks without structure
JSON-like dict protocolsComparisons with side effects
Command parsingEarly-return guard clauses

best practice

Do not rewrite every if-chain as match. Prefer match when the shape of data carries meaning.
Exhaustiveness Patterns
exhaust.py
Python
1from enum import Enum, auto
2from typing import NoReturn
3
4class Color(Enum):
5 RED = auto()
6 GREEN = auto()
7 BLUE = auto()
8
9def assert_never(x: NoReturn) -> NoReturn:
10 raise AssertionError(f"unexpected: {x!r}")
11
12def hex_color(c: Color) -> str:
13 match c:
14 case Color.RED:
15 return "#f00"
16 case Color.GREEN:
17 return "#0f0"
18 case Color.BLUE:
19 return "#00f"
20 case _ as unreachable:
21 assert_never(unreachable)
Pitfalls
  • case x: always matches — order matters
  • case [x]: matches length-1 sequences, not only lists
  • Constants must be dotted (case Status.OK) or the name is a capture
  • Guards run only after the pattern succeeds
  • Subject is evaluated once — good for expensive expressions
pitfall.py
Python
1STATUS_OK = 200
2
3def bad(code: int) -> str:
4 match code:
5 case STATUS_OK: # BUG: this CAPTURES into STATUS_OK locally in 3.10 style?
6 # Actually bare names in case are capture patterns!
7 return "ok"
8 case _:
9 return "other"
10
11def good(code: int) -> str:
12 match code:
13 case 200:
14 return "ok"
15 case _:
16 return "other"
17
18# Use dotted names for constants:
19class C:
20 OK = 200
21
22def better(code: int) -> str:
23 match code:
24 case C.OK:
25 return "ok"
26 case _:
27 return "other"

danger

A bare name in a case clause is a capture pattern, not a constant lookup. Use literals or dotted attributes for constants.
Nested & Real-World JSON
nested.py
Python
1def handle_api(payload: dict) -> str:
2 match payload:
3 case {"data": {"user": {"id": int(uid), "name": str(name)}}}:
4 return f"user {uid}:{name}"
5 case {"errors": [{"message": msg}, *_]}:
6 return f"error {msg}"
7 case {"data": list(items)} if items:
8 return f"{len(items)} items"
9 case _:
10 return "unrecognized"
11
12print(handle_api({"data": {"user": {"id": 1, "name": "Ada"}}}))
13print(handle_api({"errors": [{"message": "nope"}]}))
Combining with Assignment Expressions
combo_walrus.py
Python
1import re
2
3def extract(line: str) -> str | None:
4 match line.split():
5 case ["ERR", code] if (m := re.fullmatch(r"E\d{3}", code)):
6 return m.group(0)
7 case ["INFO", *msg]:
8 return " ".join(msg)
9 case _:
10 return None
11
12print(extract("ERR E404"))
13print(extract("INFO hello world"))
Matching Enums
enum_match.py
Python
1from enum import Enum, auto
2
3class Cmd(Enum):
4 START = auto()
5 STOP = auto()
6 RESTART = auto()
7
8def run(cmd: Cmd) -> str:
9 match cmd:
10 case Cmd.START:
11 return "starting"
12 case Cmd.STOP:
13 return "stopping"
14 case Cmd.RESTART:
15 return "restarting"
16
17print(run(Cmd.START))
$Blueprint — Engineering Documentation·Section ID: PYTHON-MATCH·Revision: 1.0

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.