Why this matters
Programs constantly need to react to the shape of data: a command typed by a user (move north, take lamp, quit), a message from a network (["ping"], ["data", payload]), a point that might be on an axis. With if/elif you write a test for the length, then index into the parts, then compare, again and again. Python 3.10 added the match statement, which checks the shape of a value and pulls out its parts in one step. For simple value comparisons it is a tidy alternative to elif chains; for structured data it is much clearer.
A first match
command = input()
match command:
case "start":
print("starting")
case "stop":
print("stopping")
case _:
print(f"unknown command: {command}")
With input stop, this prints stopping.
The parts:
matchis followed by the subject, the value being examined.- Each
casegives a pattern. Python tries the cases top to bottom, and runs the block of the first pattern that matches. No other case runs, and there is no "fall-through" to the next case as in C'sswitch. _is the wildcard: it matches anything. Acase _:at the end catches everything else, likeelse.- If no case matches and there is no wildcard, nothing happens; the statement just ends.
Literal patterns
Numbers, strings, True, False and None written directly in a pattern are literal patterns. They match a subject that is equal (with ==; for True, False and None, with is).
code = int(input())
match code:
case 200:
print("OK")
case 404:
print("Not Found")
case _:
print("something else")
Or-patterns
| combines alternatives; the case matches if any of them does:
day = input().lower()
match day:
case "saturday" | "sunday":
print("weekend")
case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
print("weekday")
case _:
print("not a day")
So far this is just a neater elif chain. The real power comes next.
Capture patterns
A bare name in a pattern is a capture pattern: it matches anything, and binds that name to the value it matched.
match input():
case "quit":
print("bye")
case other:
print(f"you said {other!r}")
With input hello, it prints you said 'hello'. other works like _ except that it keeps the value. (_ is special: it never binds.)
Warning: Because a bare name captures, you cannot compare with a variable by writing its name.
case QUIT:withQUIT = "quit"defined earlier does not test equality; it matches everything and rebindsQUIT. Use the literalcase "quit":, a guard (case x if x == QUIT:), or a dotted name likecase config.QUIT:(dotted names are treated as values to compare with, not captures).
Sequence patterns
A pattern in square brackets (or parentheses) matches a sequence of a specific length and matches each element against a sub-pattern. This is where match shines. Combined with str.split(), which turns a line into a list of words, you can parse commands elegantly:
line = input()
match line.split():
case ["go", direction]:
print(f"you walk {direction}")
case ["take", item]:
print(f"you pick up the {item}")
case ["look"]:
print("you see a dark room")
case []:
print("say something")
case _:
print("I don't understand")
| Input | Words | Matching case | Output |
|---|---|---|---|
go north | ['go', 'north'] | ["go", direction] | you walk north |
take lamp | ['take', 'lamp'] | ["take", item] | you pick up the lamp |
look | ['look'] | ["look"] | you see a dark room |
| (empty line) | [] | [] | say something |
go | ['go'] | _ | I don't understand |
Each sequence pattern checks three things at once: that the subject is a sequence, that it has the right length, and that each element matches its sub-pattern (a literal like "go" must be equal; a name like direction captures). Writing that with if statements takes noticeably more code:
words = line.split()
if len(words) == 2 and words[0] == "go":
direction = words[1]
print(f"you walk {direction}")
elif ...
Note: Strings are sequences too, but sequence patterns deliberately do not match
str,bytesorbytearraysubjects; otherwisecase [a, b]:would match every two-character string, which is rarely what you mean. Lists and tuples (and ranges) are matched.
Star patterns: variable length
*name inside a sequence pattern matches zero or more elements, binding them as a list:
match input().split():
case ["echo", *words]:
print(" ".join(words))
case [first, *_, last]:
print(f"from {first} to {last}")
case _:
print("too short")
| Input | Output |
|---|---|
echo hello there | hello there |
echo | (an empty line: words is []) |
a b c d | from a to d |
x | too short |
At most one star is allowed per sequence pattern. *_ matches any number of elements without binding them.
Matching tuples
A pair of values in parentheses, like (x, y), is a tuple: a fixed sequence (covered properly in the collections module). Matching on a tuple lets you test several values at once:
match (x, y):
case (0, 0):
print("origin")
case (a, 0):
print(f"on the x-axis at {a}")
case (0, b):
print(f"on the y-axis at {b}")
case (a, b):
print(f"at {a}, {b}")
Here literals and captures mix freely: (a, 0) means "any pair whose second element equals 0; call the first one a".
Guards
A pattern can be followed by if condition, called a guard. The case matches only if the pattern matches and the guard is truthy. Names captured by the pattern can be used in the guard:
match input().split():
case ["withdraw", amount] if int(amount) > 500:
print("limit exceeded")
case ["withdraw", amount]:
print(f"dispensing {amount}")
case _:
print("invalid")
With withdraw 800: limit exceeded. With withdraw 40: dispensing 40. If the guard fails, Python moves on and tries the next case, so order the specific (guarded) case before the general one.
How Python matches, underneath
match is not a jump table. Python compiles it into a sequence of tests, tried in order, just like an if/elif chain but generated for you: for a sequence pattern, check that the subject is a sequence (and not a string), check its length, then compare or bind each element. There are dedicated bytecode instructions for parts of this (MATCH_SEQUENCE, GET_LEN), visible with dis. So performance is similar to the equivalent if chain; the gain is clarity, not speed.
There are more pattern kinds that you will be ready for later: mapping patterns for dictionaries (case {"type": "click", "x": x}:) and class patterns for objects (case Point(x=0, y=0):), plus as to capture a sub-pattern (case ["go", ("north" | "south") as d]:).
When to use match
- Use
matchwhen you are dispatching on the structure of data: commands, messages, tuples of values, nested data. - Use it for a long list of alternatives for one value, especially with
|. - Use plain
iffor range checks and general conditions (if 90 <= score <= 100); expressing those as guards oncase _is clumsy.
Common mistakes
- Comparing with a variable by name.
case limit:captures instead of comparing. In a file with more cases after it, Python stops you withSyntaxError: name capture 'limit' makes remaining patterns unreachable. - Putting
case _:first. It matches everything, so later cases can never run; Python reportsSyntaxError: wildcard makes remaining patterns unreachable. - Forgetting that captured values are strings. After
case ["add", x, y]:on split words,xis"2", not2. Convert withint. - Expecting a string to match a sequence pattern.
match "ab": case [a, b]:does not match. - Older Python.
matchneeds Python 3.10 or newer; in older versions it is aSyntaxError. (The sandbox has 3.13.)
Recap
match subject:tries eachcase pattern:in order and runs only the first one that matches;case _:is the catch-all.- Literal patterns compare values;
|gives alternatives; bare names capture whatever they match. - Sequence patterns like
["go", direction]check type and length and destructure in one step;*restmatches a variable number of elements. Strings are not matched as sequences. - Guards (
case pattern if condition:) add arbitrary conditions using captured names. - Use
matchfor the shape of data; useiffor general conditions.
Going further: read PEP 636, "Structural Pattern Matching: Tutorial" (peps.python.org/pep-0636), which builds a text adventure step by step and covers mapping and class patterns.