šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Python Structural Pattern Matching

Master Python 3.10's match/case statement — real structural matching on shape and type, not just a switch statement in disguise.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

In case (x, 0):, what does x become inside that branch?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

match/case, introduced in Python 3.10 via PEP 634, looks like a switch statement at first glance but is fundamentally more powerful: it matches on the structure of a value — its type, its contents, its attributes — not just equality. This lesson teaches you to use it idiomatically instead of as a worse if/elif chain.

1Beyond switch: What "Structural" Actually Means

A traditional switch statement in languages like C or Java compares a single scalar value for equality against a list of constants. Python's match/case can do that — case 200: behaves exactly like you'd expect — but that's the simplest, least interesting case it supports. The 'structural' in structural pattern matching means each case describes a *shape*: a sequence of a certain length, a mapping with certain keys, an instance of a certain class with certain attribute values — and the match succeeds only if the subject's actual structure fits that shape.

Critically, match/case doesn't just test structure — it simultaneously *destructures* and *binds* matching parts to new names in one motion. case (x, 0): both checks 'is this a 2-tuple whose second element is 0' and binds x to the first element, available for use in the case body. Achieving the same thing with if/elif requires separate length and equality checks followed by manual indexing — more code, more chances for an off-by-one mistake.

This is precisely why pattern matching pairs so naturally with the discriminated unions from the previous lesson: case Success(data=data): checks 'is this an instance of Success' and extracts its data attribute in the same breath, replacing an isinstance() check followed by a manual .data access with one declarative line.

āœ•
—
+
def http_status_text(code: int) -> str:
    match code:
        case 200:
            return "OK"
        case 404:
            return "Not Found"
        case _:
            return "Unknown"
localhost:3000
Console Output
describe((3, 0))
'On the x-axis at 3'

2Class Patterns: Matching Type and Attributes Together

case Success(data=data): is a class pattern. It performs an implicit isinstance(result, Success) check first — if that fails, the case doesn't match at all — and only then destructures the named attribute data into a locally bound variable of the same name. You can bind to a different local name too, with case Success(data=payload):, and you can match multiple attributes at once: case Point(x=0, y=y): matches only points on the y-axis, binding y.

For this to work on your own classes, Python needs to know which positional attributes are available for matching without keywords — that's controlled by a class attribute called __match_args__, which dataclasses (the next lesson) sets automatically based on field order. That's why case Point(0, y): (positional) works out of the box for a dataclass Point with fields x, y, without you writing any extra matching logic.

This turns match/case over a discriminated union into something close to exhaustive, type-safe branching: each case both identifies which variant you're handling and gives you direct, already-typed access to that variant's specific fields, with a final case _: to handle anything unaccounted for.

āœ•
—
+
def describe(point: tuple) -> str:
    match point:
        case (0, 0):
            return "Origin"
        case (x, 0):
            return f"On the x-axis at {x}"
        case (x, y):
            return f"Point at ({x}, {y})"
localhost:3000
Pattern Match
case Success(data=data):
isinstance check + attribute destructure, together

3Guards, Wildcards, and Writing Exhaustive Matches

A guard (case ["move", direction] if direction in (...):) lets you attach an arbitrary boolean condition to a case, evaluated only after the structural pattern already matched. This is the escape hatch for logic that can't be expressed structurally — value ranges, membership checks, cross-field comparisons — while keeping the shape-matching part of the case declarative and readable.

The wildcard _ in case _: matches anything and binds nothing; it's the idiomatic way to write a catch-all final case, equivalent to else in an if/elif chain. Unlike a bare variable name, _ is guaranteed by the language to never be treated as a capture pattern that shadows an outer variable — it's specifically the 'match anything, ignore it' pattern.

Because match doesn't require you to write an exhaustive set of cases (an unmatched value simply falls through with no error, unless you add case _: that raises), disciplined use means always ending non-trivial matches with an explicit case _: that either handles the default sensibly or raises a clear error — silently doing nothing on an unmatched pattern is a common source of confusing, hard-to-trace bugs.

āœ•
—
+
match result:
    case Success(data=data):
        print(f"Got: {data}")
    case Failure(error=msg):
        print(f"Failed: {msg}")
localhost:3000
Exhaustiveness
case _:
Explicit catch-all — never leave a match silently incomplete

4Step-by-Step Breakdown

match/case isn't Python's switch statement. It's a structural pattern matcher that can destructure, type-check, and bind variables all in one clause — let's see why that matters.

At its simplest, match/case compares a value against literal patterns, similar to a switch statement in other languages.

The real power is structural matching: you can destructure sequences and bind their elements directly in the case clause.

Checkpoint: In case (x, 0):, what does x become inside that branch?

  • →It is bound to whatever value is in the first position of the tuple
  • →It must literally match a variable named x elsewhere in scope

Class patterns match by type AND destructure attributes at once — perfect for the discriminated unions from the last lesson.

Guards add extra conditions with 'if' inside a case — the pattern must match AND the guard must be true.

Checkpoint: What does the if clause in case ["move", direction] if direction in (...) do?

  • →It adds a guard condition — the case only matches if the pattern matches AND the condition is true
  • →It always runs regardless of whether the pattern matched

Pattern matching pairs beautifully with discriminated unions and dataclasses — next, we formalize those with the dataclasses module itself.

Guard a Real Match Case. Finish http_status_text(): a guard clause applies extra logic beyond literal matching.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Always include an explicit case _: for non-trivial matches

match doesn't require exhaustiveness and silently does nothing if no case matches. An explicit wildcard case — even one that just raises ValueError(f'Unhandled case: {value}') — turns a silent bug into a loud, debuggable one.

Reach for match/case specifically when you need structure, not just equality

For a simple value-to-value lookup, a dict or a plain if/elif is often more readable than match/case. Pattern matching earns its complexity when you're destructuring sequences, mappings, or class instances.

Frequent Bugs

THE BUG

Writing case SomeClass: (uppercase, no parentheses) intending a type check, and being confused when it silently captures the value as a variable named SomeClass instead.

THE FIX

Bare names in a pattern are always capture patterns, even if they happen to match a class name in scope. For a type check, use case SomeClass():, with parentheses, which is a class pattern.

Real-World Examples

Routing a Simple Command-Line Interpreter

A REPL-style tool needs to parse space-separated user commands like "move up" or "quit" into distinct actions, with graceful handling of malformed input.

def handle(command: str) -> str:
    match command.split():
        case ["quit"]:
            return "Goodbye!"
        case ["move", direction] if direction in ("up", "down", "left", "right"):
            return f"Moving {direction}"
        case ["move", *rest]:
            return f"Invalid move arguments: {rest}"
        case _:
            return f"Unknown command: {command}"

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Writing case MyClass: without parentheses expecting a type check, but it silently binds the value to a new variable named MyClass instead.

# Wrong: MyClass here is a capture pattern, not a type check match value: case MyClass: print("matches everything, binds to MyClass") # Correct: class pattern with parentheses match value: case MyClass(): print("only matches actual MyClass instances")

The Solution //

Use case MyClass(): (with parentheses) for a class pattern that performs isinstance and can destructure attributes; bare names without parentheses are always capture patterns.

Lesson Glossary

[01]match/case

Python 3.10's structural pattern matching statement (PEP 634), which matches a subject value against a series of patterns describing its shape, type, or contents.

Code Preview
// match/case context

[02]Capture pattern

A bare lowercase name in a case clause that binds to whatever value occupies that position, rather than being compared for equality.

Code Preview
// Capture pattern context

[03]Class pattern

A case pattern like ClassName(attr=value) that checks isinstance and destructures named or positional attributes in one step.

Code Preview
// Class pattern context

[04]Guard clause

An optional if condition attached to a case, evaluated only after the structural pattern matches, adding an extra boolean requirement.

Code Preview
// Guard clause context

Continue Learning