Python match (Pattern Matching): How Structural Pattern Matching Works
match lets Python choose one branch from many by comparing a value against patterns, not just by testing one boolean condition at a time. It is useful when you need to inspect the shape of data, such as lists, tuples, dictionaries, or custom objects, and handle each shape clearly. This article shows how Python pattern matching works, how to write common patterns, what usually goes wrong, and when to use it instead of ordinary if statements.
Difficulty: Intermediate
Helpful to know first: basic Python syntax, if/elif/else, functions, and common data types like lists, tuples, and dictionaries.
How Python match Chooses One Case From Many
The match statement compares one subject value against a series of case patterns. The first pattern that fits wins, and Python runs only that block. If no pattern fits, Python skips to case _ when you provide one, or it does nothing if you do not.
This is different from a long chain of if statements. In a match block, you describe the shape or contents you expect, and Python does the comparison for you.
command = "start"
match command:
case "start":
print("Starting")
case "stop":
print("Stopping")
case _:
print("Unknown command")
# Output: StartingIn this example, command is the subject. Python checks each case from top to bottom. The first pattern that matches the string "start" prints Starting.
The underscore pattern case _ is a catch-all. It matches anything, so it belongs last unless you want to stop all other cases from running.
What You Can Match in Python 3.12
Python pattern matching is built for structure. You can match simple literals, capture values into names, match sequences like lists and tuples, match mappings like dictionaries, and match objects by their attributes. You can also combine patterns with | and add extra conditions with a guard.
Here is the smallest useful mental model:
- case 10 means “match the value 10.”
- case name means “capture the value in a new variable named name.”
- case [x, y] means “match a two-item sequence and unpack it.”
- case {"key": value} means “match a dictionary with this key.”
- case _ means “match anything.”
The biggest surprise for beginners is that names inside a pattern usually capture values. They are not compared to existing variables unless the pattern syntax makes that literal comparison unambiguous.
Matching Simple Values and Capturing Names
Literal patterns are the easiest way to use match. They are useful when the value is one of a few exact options. Capture patterns are useful when you want the actual value, not just a yes or no answer.
Matching exact literals
This version is similar to comparing against a menu of accepted strings or numbers. It is a good fit for commands, status codes, and small enumerations.
status = 404
match status:
case 200:
print("OK")
case 404:
print("Not found")
case 500:
print("Server error")
case _:
print("Other status")
# Output: Not foundThis code shows that a literal pattern only matches the same exact value. Since status is 404, Python jumps to the case 404 branch.
Capturing part of a result
Capture patterns are common when one case should handle many values, but still needs the matched value later. The captured name becomes a new local variable inside that case block.
point = 7
match point:
case n:
print(f"Got number: {n}")
# Output: Got number: 7Because case n is a capture pattern, it matches anything and stores the subject in n. This is a common source of confusion: n is not being compared to an existing variable named n here.
Matching Lists and Tuples by Shape
Sequence patterns are one of the main reasons to use match. They let you describe the length and layout of a list or tuple directly. This is cleaner than indexing into the sequence and checking its length in separate steps.
Matching exact sequence lengths
When you know the data shape, you can write the shape into the pattern. Python checks both the number of items and their positions.
coords = [3, 4]
match coords:
case [x, y]:
print(f"x={x}, y={y}")
case [x]:
print(f"One item: {x}")
case _:
print("Unexpected shape")
# Output: x=3, y=4The first case matches because coords has exactly two items. Python assigns the first item to x and the second item to y.
Using a star pattern for “the rest”
If you care about the first few items and want to ignore the rest, use a starred name. This is useful for logs, command arguments, and records with extra fields.
data = ["error", 500, "retry later"]
match data:
case ["error", code, *details]:
print(code)
print(details)
# Output: 500
# Output: ['retry later']The starred name collects the remaining items as a list. Here, details becomes a list containing one string.
Matching Dictionaries by Key
Mapping patterns let you match dictionaries by required keys. This is useful for decoded JSON, request payloads, configuration data, and other nested records.
user = {"name": "Ada", "role": "admin"}
match user:
case {"name": name, "role": "admin"}:
print(f"Admin user: {name}")
case {"name": name}:
print(f"Regular user: {name}")
case _:
print("Unknown record")
# Output: Admin user: AdaOnly the keys named in the pattern are required. Extra keys are allowed. That means {"name": "Ada", "role": "admin", "id": 7} would still match the first case.
Dictionary patterns do not care about key order. They care about whether the required keys are present and whether the matched values fit the pattern.
Matching Multiple Options and Adding Guards
Pattern matching becomes more useful when you combine patterns. The | operator means “either of these patterns.” A guard adds a normal boolean condition that must also be true.
Using one case for several values
When several inputs should lead to the same action, an OR pattern keeps the code short and clear.
day = "Sat"
match day:
case "Sat" | "Sun":
print("Weekend")
case _:
print("Weekday")
# Output: WeekendThe subject only needs to match one side of the pattern. Here, day matches "Sat", so the first branch runs.
Adding a guard after a successful pattern
A guard filters matches further. The pattern must match first, and then the guard expression must evaluate to true.
score = 82
match score:
case n if n >= 90:
print("A")
case n if n >= 80:
print("B")
case _:
print("Lower grade")
# Output: BHere, score matches the second case because the value is at least 80. Guards are useful when the pattern alone is not specific enough.
How Class Patterns Work With Objects
Class patterns let you match objects by type and, optionally, by their attributes. This is powerful when working with dataclasses, simple model objects, and parsed records.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p = Point(2, 5)
match p:
case Point(x, y):
print(f"Point at {x}, {y}")
case _:
print("Not a Point")
# Output: Point at 2, 5Python uses the class name in the pattern and then checks the object’s shape. For dataclasses, the generated field order makes this especially convenient. You can also match named attributes with keyword patterns when that reads better.
Class patterns are most useful when you control the object type. For loosely shaped data from outside your code, mapping patterns on dictionaries are often simpler and more explicit.
How match Behaves Differently From if and elif
The biggest practical difference is that match focuses on the shape of one value, while if focuses on conditions that can be any boolean expression. A match block is usually easier to read when you are branching on the same value in several different forms.
For example, compare these two versions:
command = "stop"
if command == "start":
print("Starting")
elif command == "stop":
print("Stopping")
else:
print("Unknown command")
# Output: StoppingThe if version works well, but it repeats the same variable and comparison style several times. The match version groups the possible shapes and values in one place, which can be easier to maintain when the cases get more complex.
When match is clearer than if
Use match when one value can naturally be described by several patterns: a command string, a tuple structure, a dictionary payload, or a union of several accepted forms. Use if when the logic is mostly about unrelated conditions, such as combining several different variables or checking ranges and flags in an arbitrary order.
| Situation | Better choice | Why |
|---|---|---|
| One value, many possible shapes | match | Patterns describe the data directly |
| Several unrelated conditions | if | Boolean expressions are more natural |
| Need to unpack list or dict structure | match | Can bind parts of the value in one step |
| Simple two-way branch | if | Shorter and easier to read |
Predict the Output
Capture pattern or literal comparison?
What does this print?
name = "Ada"
match name:
case name:
print("same")
case _:
print("different")Show answer
It prints same.
The name in case name is a capture pattern, so it matches anything and binds the subject to a new local name. The first case always succeeds, so Python never reaches case _.
Sequence length and starred binding
What happens?
items = [1, 2, 3]
match items:
case [first, second]:
print("two items")
case [first, *rest]:
print(first, rest)Show answer
It prints 1 [2, 3].
The first case fails because the list has three items, not two. The second case matches the first element to first and collects the remaining items into rest as a list.
Common Mistakes With Python match Patterns
Thinking an unqualified name is a literal value
Beginners often expect case color to mean “match the value stored in color.” That is not how capture patterns work.
Problem: This case matches every subject, because color is treated as a new capture name instead of a value to compare against.
color = "red"
value = "blue"
match value:
case color:
print("matched")Fix: Compare against a literal, or use a guard if you need to compare with an existing variable.
color = "red"
value = "blue"
match value:
case "red":
print("matched")
case other if other == color:
print("matched")The corrected version works because the comparison is now explicit, so Python only matches when the value really equals the existing variable.
Putting a broader case before a narrower one
match stops at the first successful case. If a broad pattern appears first, later more specific cases may never run.
Problem: The first case case [first, *rest] matches every non-empty list, so the exact two-item case below it is unreachable.
items = [1, 2]
match items:
case [first, *rest]:
print("at least one item")
case [first, second]:
print("exactly two items")Fix: Put the most specific patterns first and the broad catch-all patterns last.
items = [1, 2]
match items:
case [first, second]:
print("exactly two items")
case [first, *rest]:
print("at least one item")The corrected version works because the exact-length case gets a chance to match before the broader pattern claims the value.
Forgetting that a guard is not part of the pattern
A guard runs after the pattern has already matched. If you place an important check only in the guard, the pattern can still bind values even when the guard fails.
Problem: This code assumes the first case will handle only positive numbers, but the guard can fail after n is already bound.
n = -3
match n:
case x if x > 0:
print("positive")
case _:
print("not positive")Fix: Keep the guard if you need it, but remember that the pattern still matches the shape before the guard decides whether the case runs.
n = -3
match n:
case x if x > 0:
print("positive")
case _:
print("not positive")The corrected version behaves the same at runtime, but the important lesson is that the pattern and the guard do different jobs.
Find the Bug
Find the subtle problem in this match block. It looks like it should handle two-item coordinate pairs and longer coordinate lists, but one case is in the wrong place.
coords = [10, 20]
match coords:
case [x, *rest]:
print("long list")
case [x, y]:
print(f"pair: {x}, {y}")Show answer
The first pattern is too broad. case [x, *rest] matches any non-empty list, including a two-item list, so the second case can never run.
Fixed code:
coords = [10, 20]
match coords:
case [x, y]:
print(f"pair: {x}, {y}")
case [x, *rest]:
print("long list")The fixed version works because the exact pair case gets checked before the broader sequence pattern.
When to Use match and When to Use Something Else
Use match when one subject value can be described by several structural patterns, especially when you need to unpack parts of that value. Use if when your decision depends on several unrelated conditions or when the test is just a plain boolean expression.
| Use match when... | Use if when... |
|---|---|
| You are branching on one value with several shapes | You are combining many boolean checks |
| You want to unpack items while matching | You only need a simple yes/no decision |
| You are matching dictionaries, sequences, or objects | You are checking ranges, flags, or mixed conditions |
| Clarity improves when cases read like data shapes | Clarity improves when the logic reads like a normal condition |
A common rule of thumb: if you can describe each branch as a pattern against the same subject, match is a good candidate. If the branches are just different boolean tests, if is usually simpler.
Interview questions about Python pattern matching
What problem does match solve?
match helps you choose branches based on the structure of a value, not just its truthiness or one simple comparison. It is especially helpful for lists, tuples, dictionaries, and custom objects.
Why does case name match everything?
Because name is a capture pattern. It binds whatever value is being matched to a new local variable. If you want to compare to an existing variable, use a literal or a guard.
Why can only one case run?
Python stops at the first successful match. That makes ordering important, because broad patterns can block later specific patterns.
Try It Yourself
Write a match block that classifies a point stored as a two-item list:
- If the list is [0, 0], print origin.
- If the list is [0, y], print y-axis and the value of y.
- If the list is [x, 0], print x-axis and the value of x.
- For any other two-item list, print other point and both values.
Expected output:
y-axis 5Hint: Put the most specific pattern first. Use a guard only when the structure alone is not enough.
Show solution
point = [0, 5]
match point:
case [0, 0]:
print("origin")
case [0, y]:
print("y-axis", y)
case [x, 0]:
print("x-axis", x)
case [x, y]:
print("other point", x, y)Summary
- match chooses one branch by comparing a subject against patterns.
- Capture patterns bind names; they do not compare against existing variables.
- Sequence, mapping, and class patterns let you match the shape of data directly.
- Order matters because Python stops at the first successful case.
- Guards add extra boolean checks after the pattern matches.
- Use match when the subject has a clear structure; use if for plain boolean logic.
Next, read about Python if statements and Python dictionaries, since both help you compare when match is the clearest tool.