The match statement compares a value, called the subject, against a series of case patterns and runs the first one that fits. It arrived in Python 3.10 and is often described as Python's switch statement, but it does more: patterns can check the shape of data, such as a tuple of two items or a dictionary with certain keys, and pull values out of it at the same time.
command = "stop"
match command:
case "start":
print("Starting")
case "stop":
print("Stopping")
case _:
print("Unknown command")This lesson covers checking your Python version, the main pattern types, guards that add extra conditions, and the capture rules that surprise people coming from other languages. The examples route messages in a parcel depot.
Availability
match is new syntax, so it is checked when Python parses the file. On Python 3.9 or older, a file containing match fails with SyntaxError before a single line runs; you can't guard it with an if at run time. Check your version first:
python --versionimport sys
print(sys.version_info >= (3, 10))If your code must support older versions, use an if/elif chain or a dictionary lookup instead. match and case are soft keywords: they only act as keywords at the start of a match statement, so existing code that uses match as a variable name, such as match = re.search(...), keeps working.
Patterns
Each case holds a pattern. The common kinds are:
| Pattern | Example | Matches |
|---|---|---|
| Literal | case "stop":, case 404: | an equal value |
| OR pattern | case "y" | "yes": | any of the alternatives |
| Sequence | case ("deliver", qty): | a two-item sequence starting with "deliver" |
| Star | case ["scan", *codes]: | a sequence of any length, rest captured |
| Mapping | case {"type": "return", "id": pid}: | a dict with those keys (extra keys allowed) |
| Class | case Parcel(weight=w): | an instance of Parcel, capturing an attribute |
Sequence patterns match lists and tuples but deliberately not strings. Mapping patterns ignore any extra keys, which suits JSON messages that carry more fields than you need. This structural checking is what makes match more than a switch: one case can verify the type, the length and specific values, and unpack the rest.
Guards
A guard is an if condition written after a pattern. The case runs only if the pattern matches and the guard is true:
match message:
case ("deliver", qty) if qty > 0:
print("Pack", qty)
case ("deliver", qty):
print("Invalid quantity:", qty)The guard is checked only after the pattern succeeds, so it can use the captured qty. If the guard is false, Python moves on to the next case. A guard can't rescue a pattern that didn't match: ("collect", 3) never reaches the guard of a ("deliver", qty) case.
Put more specific cases first. Python tries cases from top to bottom and stops at the first match, exactly like an if/elif chain.
Captures
A bare name in a pattern captures whatever is in that position; it does not compare against an existing variable with that name:
LIMIT = 10
match 25:
case LIMIT: # captures 25 into LIMIT; matches anything
print(LIMIT) # 25Python actually rejects this when another case follows, with "name capture 'LIMIT' makes remaining patterns unreachable". To compare against a constant, use a dotted name (case Limits.MAX:), an enum member, a literal, or a guard (case x if x == LIMIT:).
The underscore _ is the wildcard: it matches anything and binds nothing. case _: is the conventional fallback and must come last, since every later case would be unreachable. Inside larger patterns, _ ignores positions you don't care about, as in case ("deliver", _):.
Independent practice and a worked check
Task: route parcel commands with a sequence pattern, a positive quantity guard and a final fallback. Handle ("deliver", n) with n > 0, ("cancel", parcel_id), and anything else.
def handle(command):
match command:
case ("deliver", qty) if qty > 0:
return f"Deliver {qty}"
case ("cancel", parcel_id):
return f"Cancel {parcel_id}"
case _:
return "Check the message"
for cmd in [("deliver", 2), ("deliver", 0), ("cancel", "P-7"), ("lost",)]:
print(handle(cmd))This prints Deliver 2, Check the message, Cancel P-7 and Check the message. The zero quantity matches the pattern but fails the guard, so it falls through to the wildcard. For the worked check, a bare name in a pattern normally captures a value; it does not compare against a variable of the same name.
Reference and next practice
PEP 636 is the official pattern matching tutorial, and the match statement reference defines every pattern. Revisit if, elif and else for the alternative, then practise with the Python exercises and Python quiz.
The match Statement Summary
- Availability: match is structural pattern matching introduced in Python 3.10.
- Patterns: Literal, sequence and mapping patterns inspect values and their structure.
- Guards: A case guard checks an additional condition after its pattern succeeds.
- Captures: A bare name captures the subject while underscore ignores it.
The match Statement Examples
Each example shows the code, the output it produces and what each part does.
The match Statement in a small program
message = ("deliver", 4)
match message:
case ("deliver", quantity) if quantity > 0:
print("Pack", quantity)
case _:
print("Check the message")Pack 4Line-by-line explanation
- Line 1The subject is a two-item tuple.
- Line 3The sequence pattern matches the literal
"deliver", captures 4 intoquantity, and the guard confirms it is positive. - Lines 5 to 6The wildcard catches every message the first case didn't handle.
Routing JSON-style messages
def route(msg):
match msg:
case {"type": "deliver", "qty": int(qty)} if qty > 0:
return f"Pack {qty}"
case {"type": "return", "id": parcel_id}:
return f"Log return {parcel_id}"
case {"type": "scan", "codes": [first, *rest]}:
return f"Scan {first} and {len(rest)} more"
case {"type": str(kind)}:
return f"Unsupported type: {kind}"
case _:
return "Malformed message"
print(route({"type": "deliver", "qty": 3, "van": 7}))
print(route({"type": "return", "id": "P-19"}))
print(route({"type": "scan", "codes": ["A1", "B2", "C3"]}))
print(route({"type": "deliver", "qty": "3"}))
print(route(["deliver", 3]))Pack 3
Log return P-19
Scan A1 and 2 more
Unsupported type: deliver
Malformed messageLine-by-line explanation
- Line 3
int(qty)is a class pattern: it matches only if the value is an int, then captures it. The extra"van"key is ignored. - Line 7A nested sequence pattern captures the first code and the rest as a list.
- Line 17
"3"is a string, so the first case fails and the general{"type": str(kind)}case catches it. - Line 18A list isn't a mapping, so only the wildcard matches.
Common The match Statement Mistakes
Expecting a bare name to compare with a variable
EXPRESS = "express"
match service:
case EXPRESS: # captures anything; SyntaxError with later cases
print("Priority")
case _:
print("Standard")match service:
case "express":
print("Priority")
case _:
print("Standard")A bare name always captures. Compare with a literal, a dotted constant such as Service.EXPRESS, or a guard.
Putting the wildcard first
match code:
case _:
print("Other")
case 404: # SyntaxError: wildcard makes this unreachable
print("Not found")match code:
case 404:
print("Not found")
case _:
print("Other")The match Statement in Real Projects
Where match statements earn their place
match is most useful when data has several possible shapes: command-line commands with different arguments, events from a message queue, parsed JSON from a web API, or nodes in a syntax tree. A single case replaces several lines of isinstance checks, length checks and indexing.
For simple value lookups, such as turning a status code into a word, a dictionary is still shorter, and for range checks like grades an if/elif chain reads better. Many teams adopt match gradually, using it where it removes nested conditionals and keeping everything else as it was.
The match Statement FAQ
Does Python have a switch statement?
Since Python 3.10, match covers everything a switch statement does and more. Before 3.10, the usual alternatives were if/elif chains and dictionaries that map values to results or functions. There is no fall-through between cases, so no break is needed.
Which Python version do I need for match case?
Python 3.10 or later. On older versions, any file containing a match statement fails to parse with SyntaxError, so the code can't even start. Check with python --version.
Can one case match several values?
Yes. Separate alternatives with |: case "y" | "yes" | "Y":. If you need the matched value, add as: case ("y" | "yes") as answer:.
Is match faster than if/elif?
Not meaningfully for typical code. Python checks cases in order, much like an if chain. Choose match for readability when you branch on the structure of data, not for performance.
Can I match on the type of a value?
Yes, with class patterns: case int(): matches any integer and case str(text): matches a string and captures it. For your own classes, case Point(x=0, y=y): checks the type and attributes at once.
The match Statement Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
What does a bare name normally do in a match pattern?
Which review concern is valid for the match statement?
The match Statement Quiz
2 questions to check what you learned.
What does a bare name normally do in a match pattern?
- Look up an existing constant
- Capture a value
- Python ignores the distinction
Which statement correctly describes guards?
- A case guard checks an additional condition after its pattern succeeds.
- Only the example output matters, not the rule
- Every operation has the same behavior