Python 3.10: The One With match/case

~
~
Published on
Authors
python-3-10-banner

Python 3.10 gave developers one of those features that immediately started arguments in code reviews:

“Wait… did Python finally get a switch statement?”

Kind of.

But also, not really.

The feature is called match/case, and while it may look like a classic switch at first glance, it is actually more powerful than that. Python 3.10 introduced structural pattern matching, which means your code can branch not only based on values, but also based on the shape of the data.

That is where things get interesting.

For junior developers, match/case can make decision-heavy code easier to read.

For senior developers, it can clean up parsing logic, command handling, event processing, state transitions, and structured data workflows.

In other words: less if/elif spaghetti, more readable intent.

What is match/case?

At its simplest, match/case looks like a cleaner way to handle multiple possible values.

status = 404

match status:
    case 200:
        print("OK")
    case 404:
        print("Not Found")
    case 500:
        print("Server Error")
    case _:
        print("Unknown Status")

That already feels nicer than a long if/elif/else chain:

status = 404

if status == 200:
    print("OK")
elif status == 404:
    print("Not Found")
elif status == 500:
    print("Server Error")
else:
    print("Unknown Status")

But this is only the beginner-level use case.

The real value of match/case is not just matching values.

It is matching patterns.

It is not just a switch statement

If you are coming from Java, JavaScript, C#, or another language with switch, it is tempting to think of Python’s match/case as the same thing with different syntax.

That is not quite right.

A traditional switch usually checks one value against several possible cases.

Python’s match/case can do that, but it can also match against:

  • literals
  • tuples
  • lists
  • dictionaries
  • class-like structures
  • nested data
  • wildcard fallbacks
  • captured values

So instead of asking only:

“Does this value equal 404?”

You can also ask:

“Does this data have the shape I expect?”

That is a much bigger idea.

Why match/case matters

Before Python 3.10, developers often handled structured data with repeated dictionary checks.

For example:

command = {"action": "create", "resource": "user"}

if command.get("action") == "create":
    if command.get("resource") == "user":
        print("Create user")
elif command.get("action") == "delete":
    if command.get("resource") == "user":
        print("Delete user")
else:
    print("Unknown command")

This works.

But it is not especially pleasant to read.

The logic is hidden inside repeated checks. You have to mentally connect the conditions to understand what shape of data each branch expects.

With match/case, the same logic becomes more expressive:

command = {"action": "create", "resource": "user"}

match command:
    case {"action": "create", "resource": "user"}:
        print("Create user")
    case {"action": "delete", "resource": "user"}:
        print("Delete user")
    case _:
        print("Unknown command")

Now the code describes the structure directly.

You are not just checking values.

You are saying:

“When the data looks like this, do this.”

That shift is what makes match/case useful.

Matching tuples

Pattern matching works nicely with tuples because tuples often represent simple structured data.

point = (0, 5)

match point:
    case (0, 0):
        print("Origin")
    case (0, y):
        print(f"Y-axis at {y}")
    case (x, 0):
        print(f"X-axis at {x}")
    case (x, y):
        print(f"Point at {x}, {y}")

This example does more than compare values.

It also unpacks the tuple.

So when Python sees this:

case (0, y):

It means:

“Match a tuple where the first value is 0, and capture the second value as y.”

That is clean, readable, and expressive.

Matching dictionaries

Dictionaries are where match/case can become especially useful in real projects.

Imagine your application receives different events:

event = {
    "type": "user.created",
    "payload": {
        "id": 123,
        "name": "Ada"
    }
}

You can match against the shape of that event:

match event:
    case {"type": "user.created", "payload": {"id": user_id, "name": name}}:
        print(f"Create user {name} with ID {user_id}")

    case {"type": "user.deleted", "payload": {"id": user_id}}:
        print(f"Delete user with ID {user_id}")

    case _:
        print("Unknown event")

This is where match/case starts to feel less like syntax sugar and more like a readability tool.

The structure of the data is visible in the structure of the code.

The wildcard case

The underscore _ is the fallback case.

case _:
    print("Unknown command")

It means:

“If nothing else matched, do this.”

You will see this a lot in match/case examples because it gives you a safe default.

Without it, your code may simply do nothing if no pattern matches.

That can be fine in some cases, but for application logic, a clear fallback is usually better.

A practical example: command routing

One useful place for match/case is command routing.

Imagine you are building a small CLI tool:

command = ("deploy", "production")

match command:
    case ("deploy", environment):
        print(f"Deploying to {environment}")

    case ("rollback", environment):
        print(f"Rolling back {environment}")

    case ("status",):
        print("Checking status")

    case _:
        print("Unknown command")

This is easy to scan.

Each case describes a possible command shape.

You can see the contract immediately:

  • ("deploy", environment)
  • ("rollback", environment)
  • ("status",)

That is much clearer than spreading the same logic across several index checks and nested conditionals.

When match/case is a good fit

match/case is useful when your branching logic depends on structure.

Good use cases include:

1. Command parsing

If your app receives CLI commands, bot commands, or API instructions, pattern matching can make routing logic easier to follow.

2. Event handling

If your system processes events with different shapes, match/case can make those event branches more explicit.

3. Working with nested data

Instead of writing repeated dictionary checks, you can match against the nested structure directly.

4. State transitions

If your code reacts to different states and inputs, match/case can make the possible transitions easier to see.

5. Replacing repetitive if/elif chains

Not every conditional needs match/case, but when the branches start repeating the same kind of checks, it may be time to consider it.

When not to use match/case

Like every shiny language feature, match/case can be overused.

Do not use it just because it looks modern.

Avoid it when:

  • a simple if is clearer
  • the pattern is too clever
  • the match block becomes deeply nested
  • your team is not comfortable reading it yet
  • you are trying to force every conditional into the same style

For example, this does not need match/case:

if is_admin:
    allow_access()
else:
    deny_access()

That is already clear.

Using match/case there would not improve anything.

Readable code wins.

Always.

Junior developer takeaway

If you are still learning Python, do not think of match/case as something you need to use everywhere.

Think of it as another tool.

Before reaching for it, make sure you are comfortable with:

  • variables
  • conditionals
  • lists
  • dictionaries
  • tuples
  • functions
  • unpacking

Once those basics feel natural, match/case will make much more sense.

The goal is not to write fancy Python.

The goal is to write Python that is easier to understand.

Senior developer takeaway

For experienced developers, match/case is useful because it can reduce noise in business logic.

It can make code easier to scan when working with:

  • messages
  • commands
  • events
  • structured payloads
  • state machines
  • parsing logic

But it will not fix poor design by itself. Bad architecture with match/case is still bad architecture. The feature works best when the data model is already clear and the branching logic has a natural structure. Used well, it helps the code say what it means.

The real lesson

Python 3.10 did not just add a new syntax trick.

It gave developers a more expressive way to describe how code should react to structured data. That matters because code is read more often than it is written. Great code is not just about making the computer understand you. It is about making the next developer understand you faster. That is why match/case is worth learning.

Not because it is flashy. Not because it replaces every if. But because, in the right place, it makes intent easier to see. And clarity is one of the best features any language can ship.