Table of Contents

    Pattern Matching

    PYTHON & PROGRAMMING

    Pattern Matching — The Complete Guide

    Understand structural pattern matching with Python's match/case statement — a powerful, readable alternative to long if-elif chains.

    Introduction

    Pattern matching is a technique that checks a value against a structure or shape and, when it matches, extracts data from it. Python 3.10 introduced structural pattern matching via the match/case statement — far more powerful than a simple switch. It can match literals, types, sequences, mappings, and even the internal structure of objects, binding parts of the data to variables as it goes.

    In one line: Pattern matching lets you say "if this data looks like this shape, unpack it and act" — cleanly and readably.

    Real-World Analogy

    The Mail Sorting Machine

    A postal sorter looks at each envelope's shape and label: letters go one way, parcels another, priority mail a third. It matches each item against known patterns and routes it accordingly. match/case is that sorter for your data — it inspects the shape and sends execution down the right path.

    Prerequisites

    Before You Start

    • Python 3.10 or newer (structural pattern matching is not available earlier)
    • Understanding of if/elif/else conditionals
    • Familiarity with lists, tuples, dictionaries, and classes
    • Basic knowledge of tuple unpacking (e.g., a, b = point)
    • A Python 3.10+ interpreter to run the examples

    Basic Syntax

    The match statement compares a subject value against several case patterns, running the first that matches.

    def http_status(code):
        match code:
            case 200:
                return "OK"
            case 404:
                return "Not Found"
            case 500:
                return "Server Error"
            case _:                 # wildcard — matches anything
                return "Unknown"
    
    print(http_status(404))   # Not Found
    Note The underscore _ is the wildcard pattern — it matches anything and acts like the default case.

    Core Pattern Types

    1

    Literal & Capture Patterns

    Match exact values or bind to a name.

    A literal like 200 matches that value; a bare name like x captures the subject into the variable x.

    2

    Sequence Patterns

    Match lists/tuples by shape.

    [x, y] matches a two-element sequence and binds its items; [first, *rest] captures the remainder.

    3

    Mapping & Class Patterns

    Match dicts and object structure.

    {"key": value} matches dictionaries; Point(x=0, y=0) matches objects and extracts attributes.

    Matching Sequences

    Pattern matching shines when destructuring lists and tuples.

    def describe(point):
        match point:
            case [0, 0]:
                return "Origin"
            case [0, y]:
                return f"On Y-axis at {y}"
            case [x, 0]:
                return f"On X-axis at {x}"
            case [x, y]:
                return f"Point at ({x}, {y})"
            case _:
                return "Not a 2D point"
    
    print(describe([0, 5]))   # On Y-axis at 5
    print(describe([3, 4]))   # Point at (3, 4)

    Capturing the rest with *

    match [1, 2, 3, 4]:
        case [first, *rest]:
            print(first)   # 1
            print(rest)    # [2, 3, 4]

    Matching Dictionaries

    def handle(event):
        match event:
            case {"type": "click", "x": x, "y": y}:
                return f"Click at ({x}, {y})"
            case {"type": "key", "value": v}:
                return f"Key pressed: {v}"
            case {"type": t}:
                return f"Unhandled event: {t}"
    
    print(handle({"type": "click", "x": 10, "y": 20}))   # Click at (10, 20)

    Matching Class Instances

    You can match against object structure and pull out attributes directly.

    from dataclasses import dataclass
    
    @dataclass
    class Point:
        x: int
        y: int
    
    def locate(p):
        match p:
            case Point(x=0, y=0):
                return "Origin"
            case Point(x=0, y=y):
                return f"On Y-axis at {y}"
            case Point(x=x, y=0):
                return f"On X-axis at {x}"
            case Point(x=x, y=y):
                return f"At ({x}, {y})"
    
    print(locate(Point(0, 7)))   # On Y-axis at 7

    Guards (Extra Conditions)

    Add an if guard to a case to match only when an extra condition is true.

    def classify(point):
        match point:
            case [x, y] if x == y:
                return "On the diagonal"
            case [x, y] if x > 0 and y > 0:
                return "First quadrant"
            case [x, y]:
                return "Somewhere else"
    
    print(classify([4, 4]))   # On the diagonal
    print(classify([2, 5]))   # First quadrant

    OR Patterns and Binding

    def category(command):
        match command:
            case "start" | "run" | "go":       # OR pattern
                return "Begin execution"
            case "stop" | "halt" | "end":
                return "Stop execution"
            case str() as text:                # capture with type check
                return f"Unknown command: {text}"
    
    print(category("go"))     # Begin execution
    print(category("pause"))  # Unknown command: pause

    match/case vs if/elif

    Aspect match/case if/elif
    Best for Matching structure/shape of data Arbitrary boolean conditions
    Destructuring Built-in (binds variables) Manual unpacking needed
    Readability Clean for many shapes Gets verbose with many branches
    Python version 3.10+ All versions

    Readability Insight

    Handling \(N\) distinct data shapes with nested if/elif often needs manual type checks and unpacking, growing the cognitive load roughly with the number of conditions per branch. With pattern matching, each shape maps to a single declarative case:

    \[ \text{Branches} = N \qquad\Rightarrow\qquad \text{Cases} = N \;\text{(one clean case each)} \]

    The win isn't algorithmic speed — it's clarity: one shape, one case, with automatic binding.

    Best Practices

    Do This

    • Use match for matching data shape; keep if for simple boolean logic
    • Always include a wildcard case _ to handle unexpected values
    • Use guards (case ... if ...) for extra conditions instead of nesting
    • Prefer class patterns with dataclasses for clean object matching
    • Remember bare names capture — use literals or dotted names for constants
    • Order cases from most specific to most general

    Common Mistakes

    Bad Expecting case CONSTANT: to compare with a variable — a bare name captures the value instead of comparing to it.
    Good Use a dotted name like case Color.RED: or a literal to compare against a known value.
    Bad Forgetting the wildcard case _ — unmatched values silently fall through and do nothing.

    Interview Questions

    Question Short Answer
    What is structural pattern matching? Matching a value against a structure/shape and binding parts of it, via match/case.
    Which Python version introduced it? Python 3.10.
    What does case _ do? It's the wildcard — matches anything, acting as the default case.
    What is a guard? An if condition on a case that must also be true for it to match.
    How is it different from a switch? It matches structure and destructures data, not just equality on a value.

    Quick Revision

    Pattern Example Matches
    Literal case 200: Exact value 200
    Capture case x: Anything, binds to x
    Sequence case [a, b]: Two-element list/tuple
    Mapping case {"k": v}: Dict with key "k"
    Class case Point(x=0): Point with x == 0
    OR case "a" | "b": Either value
    Wildcard case _: Anything (default)

    Key Takeaways

    Structural pattern matching (match/case, Python 3.10+) matches data by its shape — literals, sequences, mappings, and objects — while binding the pieces you need. Use guards for extra conditions, always add a wildcard, and reach for it when if/elif chains get unwieldy.