Sequence pattern matching

Use match/case for shape-based branching without brittle index code

Sequence pattern matching, introduced in Python 3.10 (PEP 634), lets you branch on the shape of a subject instead of writing manual length and type checks. Instead of `if len(seq) == 2 and isinstance(seq[0], str) and seq[0] == "move"`, you write `case ["move", x, y]`. CPython implements this with three bytecode operations: `MATCH_SEQUENCE` verifies the subject is a sequence (excluding str, bytes, and bytearray), `GET_LEN` checks length, and `UNPACK_SEQUENCE` binds matched elements. If any check fails, the match continues to the next case. Guards with `if` add extra conditions after a pattern matches. Names are only bound on successful match, so unused names in earlier cases do not pollute the scope. <a href="/sequences-slicing">Review slice mechanics for related sequence operations</a>.

Understand.
Visualize.
Master.

Python in Depth

An interactive engineering reference for Python internals

Quick note

Specific cases before broad fallbacks.

:)
TABLE OF CONTENTS
1.2Sequence pattern matching

Use match/case for shape-based branching without brittle index code

A command arrives as a list of strings. You check its length, compare the first element, pull out the second and third by index. Three lines of boilerplate before you reach the real logic. Pattern matching collapses that into one branch.

Think of it like a package sorting machine. You drop a box on the belt, and the machine checks its shape first. If it is a small cube, it goes to bin A. If it is a long tube, bin B. The shape is checked before you open the box. That is what sequence patterns do — they branch on structure and bind names to positions only when the shape matches.

Core answer

Use match when you want one branch to both validate a shape and bind names from it. The feature was introduced in Python 3.10 and specified by PEP 634.

# [CURRENT - 3.10-3.14] Requires Python 3.10+ [PEP 634]
def handle(command: list[str]):
match command:
case ["move", x, y]:
return ("move", float(x), float(y))
case ["rotate", degrees]:
return ("rotate", int(degrees))
case ["stop"]:
return ("stop",)
case _:
raise ValueError(f"unknown command: {command!r}")

The gain is locality: length checks, literal tests, unpacking, and guards all live in one branch instead of being spread across if chains and index reads.

Step Through Pattern Matching

See how Python tests cases in order, rejects the wrong shape, binds names from the matching shape, and only then runs the selected branch.

Mechanism and bytecode

Patterns are tried top to bottom. A successful branch may:

  • verify that the subject is a compatible sequence
  • check fixed or variable length
  • compare literal components
  • bind names
  • then apply a guard

On current CPython, this is visible in dedicated bytecode such as MATCH_SEQUENCE, GET_LEN, and UNPACK_SEQUENCE.

# [CURRENT - 3.10-3.14] Requires Python 3.10+ [PEP 634]
import dis
def classify(x):
match x:
case [a, b]:
return a + b
case _:
return None
dis.dis(classify)

That bytecode detail is CPython-specific. The portable contract is the language definition in the match statement docs and PEPs.

Variable-length patterns use starred captures, which behave like structural unpacking, not regex repetition.

# [CURRENT - 3.10-3.14] Requires Python 3.10+ [PEP 634]
def parse_row(row):
match row:
case [name, email, *tags]:
return {"name": name, "email": email, "tags": tags}
case _:
raise ValueError("expected at least name and email")
print(parse_row(["Ana", "a@example.com", "admin", "billing"]))
What counts as a sequence pattern

Sequence patterns require sequence-like subjects. str, bytes, and bytearray are excluded from sequence matching despite being iterable — call .split() or similar to convert before matching.

# [CURRENT - 3.10-3.14] Requires Python 3.10+ [PEP 634]
def route(line: str):
match line.split():
case ["GET", path]:
return ("read", path)
case ["POST", path]:
return ("write", path)
case _:
raise ValueError("unsupported request line")

That rule prevents accidental character-by-character matches on text payloads, which would be error-prone at application boundaries.

Version context

match and case are soft keywords in Python 3.10+. Python 3.9 and below are End-of-Life and do not support this syntax.

# [CURRENT - 3.10-3.14] Requires Python 3.10+ [PEP 634]
match = "ordinary name outside a match statement"
case = "also ordinary here"
print(match, case)

The bytecode names shown in this guide are CPython details. The matching semantics are the language guarantee.

Edge cases and gotchas

Name captures are not value comparisons unless you use literals, guards, or dotted names. A bare name inside a pattern captures; it does not compare against an existing local variable.

Guards run only after the structural pattern matches. Keep them pure and cheap. They are part of branch selection, not a place for side effects.

# [CURRENT - 3.10-3.14] Requires Python 3.10+ [PEP 634]
def classify(point):
match point:
case [0, 0]:
return "origin"
case [x, y] if x == y:
return "diagonal"
case [x, y]:
return "point"
case _:
raise TypeError("expected a two-item sequence")

Pattern matching is not boundary validation. Decode JSON, enforce required keys, and normalize untrusted inputs before depending on structural branches for business logic.

Production usage

Use sequence patterns for command decoding, token streams, parser outputs, and small structural protocols. Use ordinary if statements for simple boolean predicates. Use function signatures when the public API should reject invalid shapes before your implementation logic begins; see .

Further depth
  • Language reference: the match statement
  • PEP 634: Structural Pattern Matching - Specification
  • PEP 635: Motivation and Rationale
  • PEP 636: Tutorial
  • dis module
BOARD NOTESContext
WHY NO BENCHMARK?

This topic is better taught with structure, semantics, and cross-references than with a synthetic chart.

Specific cases before broad fallbacks.

RELATED GUIDES
NEXT CHECKS
Contribute