Skip to Content
Match

Match (Structural Pattern Matching)

Structural pattern matching picks one branch by matching a value against patterns. Python’s match (3.10+) often replaces long ifelifelse chains when you’re branching on “one of these exact values” or on the shape of the value (e.g. how long a list is or which keys a dict has). The main gotchas come from how patterns are evaluated and how names get bound; those are worth getting straight.

Why match?

When every branch depends on the same value (a status code, a command name, the shape of some structure), ifelifelse works but gets noisy. matchcase puts the value in one place and turns each branch into a pattern: it either matches or it doesn’t. The first case that matches runs; nothing falls through to the next. Match really shines when you care about more than equality (e.g. “a list of two elements” or “a dict with key 'x'”).

Basic Syntax

You write match followed by a subject (the value you’re inspecting), then one or more case blocks. Each case has a pattern and, optionally, a guard. Python tries the patterns in order; the first one that matches runs, and the rest are skipped.

match subject: case pattern_1: block_1 case pattern_2: block_2 case _: block_default

Colons after match and after each case are required. The block under a case is whatever’s indented under it. The _ in the last case is a wildcard: it matches anything and is commonly used as the default.

Example:

status = 4 match status: case 2: label = "pending" case 4: label = "active" case 6: label = "done" case _: label = "unknown" # label is "active"

Exactly one case runs. When it’s done, execution continues after the whole match; no fall-through.

Literal Patterns

Put a number, string, None, True, or False right in the case line and you get a literal pattern. The subject is compared to that value.

Literal typeExample in caseMatches when
Integercase 4:Subject equals 4
Floatcase 2.0:Subject equals 2.0
Stringcase "ok":Subject equals "ok"
Nonecase None:Subject is None (identity)
Truecase True:Subject is True (identity)
Falsecase False:Subject is False (identity)

Important: For None, True, and False, the pattern uses identity (is), not ==. For everything else (numbers, strings), it uses equality (==). So case None: matches only the actual None object; case 0: matches when the subject equals 0.

x = 0 match x: case 0: print("zero") # runs case None: print("none") # not run match None: case None: print("none") # runs

Example with strings:

cmd = "start" match cmd: case "start": action = "run" case "stop": action = "halt" case "pause": action = "wait" case _: action = "unknown" # action is "run"

Capture Patterns

A bare name in a pattern is a capture: it matches any value and binds it to that name so you can use it in the block. It does not compare the subject to some existing variable.

value = 6 match value: case x: print(x) # 6; x is bound to the subject

So case x: means “match anything and call it x.” It does not mean “match when the subject equals the variable x.” To match a specific value, use a literal: case 6:.

Example:

data = [2, 4, 6] match data: case [a, b, c]: total = a + b + c # a=2, b=4, c=6 # total is 12

Those names only exist inside that case block, not in other cases or after the match.

Wildcard Pattern

_ matches any value and doesn’t bind it. Use it when you don’t care what the value is (e.g. for a default or “anything else”).

code = 8 match code: case 2: result = "low" case 4 | 6: result = "mid" case _: result = "other" # 8 falls here; no name bound # result is "other"

One _ per case is enough. You can repeat it in the same case; it still means “match anything.”

OR Patterns

| lets you combine several patterns in one case. The case runs if any of them matches. Python checks the subject against each pattern left to right until one matches.

match n: case 2 | 4 | 6: kind = "even_small" case 8 | 10: kind = "even_large" case _: kind = "other"

With subject 4, the first case matches and kind becomes “even_small”. The alternatives don’t bind anything; if you need the value in the block, use a capture or as.

Example:

key = "quit" match key: case "q" | "quit" | "exit": do_exit() case "s" | "save": do_save() case _: pass

AS Patterns

as lets you match a pattern and give the matched value a name: pattern as name.

match item: case [2, 4, 6] as triple: print(triple) # [2, 4, 6] case (x, y) as pair: print(pair) # the tuple (x, y)

Handy when you need the full subject (or a subpattern) in the block, not just the pieces you captured. The name after as is the value that matched the whole pattern, or the part before as in trickier patterns.

Sequence Patterns

Sequence patterns match lists, tuples, or anything else that has length and indexing. You spell out the length you expect and, optionally, subpatterns for each element.

Fixed length: A list or tuple of patterns matches a sequence of that length whose elements match. For example:

point = [2, 4] match point: case [0, 0]: label = "origin" case [x, y]: label = f"({x}, {y})" # x=2, y=4 # label is "(2, 4)"

So case [x, y]: matches any two-element sequence and binds the first to x, the second to y. Match is doing both: checking length and unpacking.

*Variable length with rest: A * in the pattern soaks up the rest into a name. You get exactly one starred part per pattern.

items = [2, 4, 6, 8] match items: case [first, *rest]: print(first, rest) # 2 [4, 6, 8]

Example – different lengths:

def describe(seq): match seq: case []: return "empty" case [a]: return f"one: {a}" case [a, b]: return f"two: {a}, {b}" case [a, b, *rest]: return f"many: {a}, {b}, rest={rest}" describe([]) # "empty" describe([4]) # "one: 4" describe([2, 6]) # "two: 2, 6" describe([2, 4, 6, 8]) # "many: 2, 4, rest=[6, 8]"

Note: Sequence patterns work on lists, tuples, and the like. They don’t match strings or bytes: in match, a string isn’t treated as a sequence of characters. For strings, use a literal or a capture.

Mapping Patterns

Mapping patterns look like dict literals: key–value pairs. They match a mapping (e.g. a dict) that has at least those keys; the values are matched (or captured) by the value patterns. Extra keys in the subject are fine.

config = {"mode": "fast", "level": 2} match config: case {"mode": "fast", "level": n}: print(n) # 2

Pattern keys must be literals (strings, numbers, etc.). Value slots can be literals or capture names. To grab the whole dict, use as: case {"mode": m} as d:.

Example:

data = {"x": 2, "y": 4, "z": 6} match data: case {"x": a, "y": b}: total = a + b # a=2, b=4; "z" ignored # total is 6

If the subject isn’t a mapping or is missing a key, the pattern fails and the next case is tried.

Class Patterns

Class patterns match by type and optionally by attributes. ClassName(arg1, arg2) matches an instance of that class whose attributes line up with the patterns. The details depend on the class; built-in types usually expose a fixed set of attributes.

from dataclasses import dataclass @dataclass class Point: x: int y: int p = Point(2, 4) match p: case Point(0, 0): label = "origin" case Point(x, y): label = f"({x}, {y})" # label is "(2, 4)"

Good for structured data (dataclasses, simple classes). To match any instance of a type without caring about fields, case Point(): does it; the parentheses are required.

Guards

A guard is an if on the end of a case. Python tries the pattern first; if it matches, it evaluates the guard. The block runs only when the pattern matches and the guard is truthy.

match value: case x if x > 0: result = "positive" case x if x < 0: result = "negative" case _: result = "zero"

The guard can use names bound in the same pattern (e.g. x in case x if x > 0:). If the guard is false, that case is treated as a non-match and the next one is tried.

Example:

pair = (2, 4) match pair: case (a, b) if a == b: kind = "equal" case (a, b) if a < b: kind = "ascending" case (a, b): kind = "descending" # kind is "ascending"

Match vs if–elif–else

Aspectmatch–caseif–elif–else
SubjectOne subject; each case is a patternEach branch has its own condition
ComparisonPattern match (literal, structure)Arbitrary Boolean expressions
Defaultcase _:else:
BindingCapture and as bind namesNo automatic binding
Best forMany discrete values or structureFew branches; complex conditions

Reach for match when you’re branching on one value against many options (literals or shapes). Use ifelifelse when you’re mixing variables, ranges, or logic that isn’t structural.

Combining Patterns

You can mix and match: case [2, 4] as pair:, case {"x": 0, "y": 0}:, case (a, b) if a < b:. _ and as work inside sequences and mappings too. Only one case runs, and order matters: first match wins.

Example – mixed:

def handle(event): match event: case {"type": "click", "x": x, "y": y} if x >= 0 and y >= 0: return f"click at ({x}, {y})" case {"type": "key", "key": k}: return f"key: {k}" case _: return "unknown"

Tricky Behaviors

Order below follows the lesson: basic syntax, literals, captures, wildcard, OR, AS, sequences, mappings, classes, guards, then general behavior.

Basic syntax: one case runs, no fall-through

Exactly one case block runs. When it finishes, execution continues after the match; nothing falls through to the next case (unlike C’s switch). No break is needed.

Basic syntax: match is a statement

match doesn’t produce a value. To use the “result” of a match, assign in each case or return from each branch inside a function.

When no case matches

If no pattern matches, the match does nothing and execution continues after it. No names get bound. Use case _: at the end if every subject should be handled.

Literal match: identity vs equality

None, True, and False use is (identity); other literals use == (equality). So case None: won’t match 0 or "", and case 0: won’t match False, even though 0 == False in Python.

Single name is always a capture

case x: matches any value and binds it to x. It does not mean “match when the subject equals the variable x.” For a specific value, use a literal: case 4: or case “ok”:.

Capture scope

Names you bind in a case (captures, as names) live only inside that block. They’re not visible in other cases or after the match. Using a name before it’s bound in that case can raise UnboundLocalError or pull in an outer scope.

Wildcard does not bind

_ matches any value and doesn’t bind it. Use it for a default branch when the value doesn’t matter. Only one _ per case is needed, but repeating it still means “match anything.”

OR patterns: alternatives don’t bind

In case 2 | 4 | 6:, the subject is checked against each pattern but none of the alternatives bind a name. If you need the value in the block, use a capture (e.g. case x: and then if x in (2, 4, 6):) or restructure.

AS pattern: what the name refers to

The name after as is the value that matched the whole pattern (or the part before as in nested patterns). So case [2, 4, 6] as triple: binds triple to the full list [2, 4, 6].

Sequence pattern and type

Sequence patterns match list and tuple, not str or bytes. case “abc”: is a literal match of the string “abc”, not “a sequence of three characters.” To branch on string length or content, use a capture plus a guard or string methods.

Empty sequence

case []: matches only an empty list (or empty tuple). It doesn’t match a one-element list or any non-empty sequence. Use *case [rest]: to match any sequence and bind its elements to rest. Only one starred part is allowed per pattern.

Mapping pattern: keys and extra keys

Pattern keys must be literals (strings, numbers, etc.). {"a": 1, "b": 2} matches a dict that has at least “a” and “b” with those values; the subject can have more keys (ignored). Missing keys or wrong values make the pattern fail and the next case is tried.

Class pattern: parentheses required

To match any instance of a type without caring about attributes, you still need parentheses: case Point():. case Point: without parentheses is a different kind of pattern (matching the class object itself).

Guard evaluation

The guard runs only after the pattern matches. Any names in the guard must be bound by that pattern. If the pattern doesn’t match, the guard never runs, so nothing is bound yet.

Order of cases: first match wins

Python tries patterns top to bottom. The first match runs; the rest are skipped. Put the more specific patterns above the broad ones. case _: or case x: at the top would match everything and the cases below would never run.

Interview Questions

Order follows the lesson: when to use match, basic behavior, literals, captures, wildcard, OR, AS, sequences, mappings, classes, guards, match vs if–elif–else, and edge cases.

When should you use match instead of if–elif–else?

Use match when you’re branching on one value against many options (literals or shapes like “list of two elements”). Use ifelifelse when conditions involve different variables, ranges, or non-structural logic.

What is the difference between match and if–elif–else?

match takes one subject and tries it against several patterns; the first matching case runs. ifelifelse evaluates a separate condition in each branch. match can bind names from patterns; ifelifelse cannot.

Does more than one case block run? Is there fall-through?

No. Exactly one case runs. When it finishes, execution continues after the match. There is no fall-through (unlike C’s switch); no break is needed.

Can match return a value?

match is a statement; it doesn’t produce a value. Assign in each case to a variable defined before the match, or put the match in a function and return from each branch.

What happens if no case matches?

Nothing. Execution continues after the match. No names get bound. Use case _: at the end if you want to handle every possible subject.

How does match compare None, True, and False vs numbers and strings?

None, True, and False use identity (is). Numbers and strings use equality (==). So case 0: and case False: are different: 0 matches the first, False matches the second.

Does case x: mean “match when subject equals variable x”?

No. A bare name is a capture: it matches any value and binds it to that name. To match a specific value, use a literal: case 4: or case “ok”:.

Where are capture and as names visible?

Only inside that case block. They’re not visible in other cases or after the match.

What does the _ pattern do?

_ is the wildcard: it matches any value and doesn’t bind it. Often used as the last case for “everything else.”

Can you match multiple values in one case? Do they bind?

Yes. Use |: case 2 | 4 | 6:. The case runs if the subject matches any of those. The alternatives don’t bind; use a capture or as if you need the value in the block.

How do you bind the whole matched value in a case?

Use as: case [a, b] as pair: binds the two-element sequence to pair; case {"x": n} as d: binds the whole mapping to d when the pattern matches. The name after as is the value that matched the whole pattern (or the part before as in nested patterns).

Do sequence patterns match strings?

No. They match list, tuple, and the like. case “abc”: is a literal match of the string “abc”, not “three characters.” To branch on string content or length, use a capture and a guard or string methods.

What does case [] match?

Only an empty list or empty tuple. It doesn’t match a one-element list or any non-empty sequence. Use *case [rest]: to match any sequence and bind its elements; only one starred part is allowed per pattern.

How do you match a dict by some keys?

Use a mapping pattern: case {"key1": v1, "key2": v2}:. The subject must be a mapping (e.g. dict) with at least those keys; values are matched or captured. Extra keys are fine. Pattern keys must be literals; missing keys or wrong values make the pattern fail.

For class patterns, how do you match any instance without caring about fields?

case Point(): with empty parentheses. The parentheses are required; case Point: without them is a different pattern (matching the class object).

What is a guard and when is it evaluated?

A guard is if condition after a pattern, e.g. case x if x > 0:. It runs only after the pattern matches. The block runs only when both match and guard are truthy. Names in the guard must come from the pattern.

Why does the order of case blocks matter?

Python tries patterns top to bottom; the first match runs and the rest are skipped. Put case _: or case x: first and it matches everything; the cases below never run. Put the specific patterns above the broad ones.

When would you use match instead of a dictionary of handlers?

match fits when the dispatch key is a value or a shape (e.g. status code, command name, list/dict structure). A dict of handlers fits when keys are fixed at write time and handlers need extra arguments or reuse. Match keeps subject and patterns in one place; a dict splits keys from logic.

Last updated on