Skip to main content

Python match-case

Satellite · Control Flow

Structural Pattern Matching

`match-case` checks the shape of your data and destructures it in the same step. Handle payloads and state machines with clarity.

Syntax​

import sys

match command:
case "quit":
sys.exit()
case "hello":
print("Hello!")
case _:
print(f"Unknown command: {command}")
  • _ is a wildcard that matches anything.
  • Requires Python 3.10+.

Used like this, match is just a switch — and if/elif would do equally well. Its real value shows when patterns describe structure.

Matching data structures​

Each case can check the type, the keys or positions, and bind the parts you need — all in one line:

match event:
case {"type": "user.created", "payload": payload}:
handle_user(payload)
case {"type": "invoice.failed", "invoice_id": inv_id}:
alert_finance(inv_id)
case [first, *rest]:
process(first, rest)
  • Dict patterns match the listed keys and ignore extra ones — exactly right for API payloads that grow fields over time.
  • Sequence patterns destructure like unpacking; *rest captures the remainder.
  • The equivalent if version needs isinstance checks plus .get() calls plus manual unpacking for every branch.

Class patterns​

Patterns match object attributes too — cleanest with dataclasses:

from dataclasses import dataclass

@dataclass
class Click:
x: int
y: int
button: str = "left"

match event:
case Click(x=0, y=0):
print("origin clicked")
case Click(x=x, y=y, button="right"):
open_context_menu(x, y)

Guards​

Guards add a Boolean condition to a structural match:

match payload:
case {"type": "user", "active": True} if payload["tier"] == "pro":
onboard_pro_user(payload)

OR-patterns​

Alternatives share one body with |:

match answer:
case "y" | "yes" | "ok":
proceed()
case "n" | "no":
abort()

The capture gotcha​

A bare name in a pattern captures — it does not compare:

RED = "red"

match color:
case RED: # matches ANYTHING and rebinds RED to it
stop()
case "blue": # unreachable — Python raises SyntaxError:
go() # "name capture 'RED' makes remaining patterns unreachable"

To compare against a constant, use a dotted name:

class Color:
RED = "red"
BLUE = "blue"

match color:
case Color.RED: # dotted = look up and compare
stop()
case Color.BLUE:
go()

match vs if/elif​

  • Use match when the shape of the data decides what happens: parsers, event handlers, command dispatch, recursive tree walks.
  • Stick with if/elif for a few literal comparisons, conditions on different variables per branch, or code that must run on Python < 3.10.
  • Order matters; the first matching case wins. Keep cases simple and extract handlers for complex logic.

Frequently Asked Questions

Does match fall through like a C switch?

No. Exactly one case body runs — the first pattern that matches — and there is no break needed and no fall-through. If nothing matches and there is no wildcard case, the match statement simply does nothing.

Why does my case comparing against a constant always match?

A bare name in a pattern is a capture, not a comparison — case LIMIT: binds LIMIT to whatever arrives. Compare via a dotted name (case Config.LIMIT:) or a literal. Python usually flags the mistake as a SyntaxError because the capture makes later cases unreachable.

match status:
case HTTPStatus.OK: # dotted name: compares
handle_ok()

Do dict patterns require an exact key match?

No — a dict pattern matches when the listed keys are present with matching values; extra keys are ignored. Sequence patterns are the opposite: [a, b] only matches a two-element sequence. Add *rest to accept longer ones.

Next up in your learning path