Free Python reference · JavaScript timeline

Python 3.10: Match data shapes and make contracts explicit

Structural matching handles shapes, type unions become shorter, and strict zip catches accidentally mismatched inputs.

Released · Editorial review October 25, 2026

Introduced in Python 3.10 · Syntax

Structural pattern matching

match tests a subject against ordered case patterns. A pattern can describe a literal, a sequence, or a mapping and can bind parts for later use. The first successful case runs; a guard adds a boolean condition after matching the structure.

In ["move", distance], the literal must match while distance captures the second item. The guard accepts only positive integers. Mapping patterns can accept extra keys, so they are useful for routing record-shaped data.

A bare name such as case expected: captures a value; it does not compare against an existing variable named expected. Use a literal, a qualified constant, or a guard for comparison. _ is a wildcard. Pattern matching is also not a substitute for validating every property of an external record.

Worked example

Minimum Python 3.10

def describe(command):
    match command:
        case ["move", distance] if isinstance(distance, int) and distance > 0:
            return f"move {distance}"
        case {"action": "stop"}:
            return "stop"
        case _:
            return "unknown"

print(describe(["move", 3]))
print(describe({"action": "stop", "source": "button"}))
print(describe(["move", -1]))

Expected output

move 3
stop
unknown

Example checked with CPython 3.14.8, CPython 3.15.0, Pyodide Python 3.13.2.

Common mistake

A capture pattern can match far more than intended. Literal strings need quotes, and case order matters when patterns overlap.

Try it yourself

Add a case for ["wait", seconds]. Decide which values seconds may take and put that rule in a guard.

Read more: Python 3.10 release notes · PEP 636

Introduced in Python 3.10 · Typing

Union types with X | Y

Use str | None to describe a value that can be a string or None. This shortens a common optional-value annotation while retaining its meaning. A return type documents the result; it does not add a default argument or a runtime validator.

Branching on is None makes the missing-value rule explicit and gives static analysis a way to narrow the remaining type. An empty string is still a string, so distinguish absence from falsey text.

Unlike parameterized collections, unions of ordinary classes can also be used with isinstance. This does not mean every possible typing expression is a valid runtime check.

Worked example

Minimum Python 3.10

def label(value: str | None) -> str:
    if value is None:
        return "missing"
    return value.upper()

print(label(None))
print(repr(label("")))
print(isinstance(3, int | str))

Expected output

missing
''
True

Example checked with CPython 3.14.8, CPython 3.15.0, Pyodide Python 3.13.2.

Common mistake

An optional type does not make the argument optional at call time. A default value is a separate part of the signature.

Try it yourself

Add a default of None, then explain the difference between calling label() and label("").

Read more: Python 3.10 release notes · PEP 604

Introduced in Python 3.10 · Built-in function

zip(strict=True) checks lengths

Ordinary zip stops when its shortest iterable ends. That is convenient for some tasks, but it can silently discard extra values when pairing names and scores. strict=True raises ValueError if their lengths differ.

The check happens while consuming the iterator rather than when creating it. If the result must be validated before further processing, materialize it or otherwise finish iteration first.

The example catches the mismatch and then shows a valid pair. Strictness checks lengths, not whether a name belongs to a score; semantic matching remains your responsibility.

Worked example

Minimum Python 3.10

try:
    list(zip(["Ada", "Mina"], [8], strict=True))
except ValueError:
    print("length mismatch")

print(list(zip(["Ada"], [8], strict=True)))

Expected output

length mismatch
[('Ada', 8)]

Example checked with CPython 3.14.8, CPython 3.15.0, Pyodide Python 3.13.2.

Common mistake

An unconsumed zip object has not checked all lengths. Side effects performed during partial iteration may already have happened before an eventual mismatch.

Try it yourself

Compare the mismatched inputs with strict=False. Which student disappears, and why could that hide a data problem?

Read more: Python 3.10 release notes · PEP 618

Recap

Structural matching handles shapes, type unions become shorter, and strict zip catches accidentally mismatched inputs. Prefer the clearest form that works on your project's supported Python versions.

Read the full Python 3.10 release notes →