Skip to content
← All Python articles

Python explainer

When to Use match Instead of if and elif

About 3 min to read. Allow extra time to work through examples.

Pick match when every branch asks about the shape of one value, and keep if and elif when the conditions test unrelated things.

This article uses Python. You can make it your practice mode to browse related content across the site.

You need a function that takes an event decoded from JSON and returns the path of a failed web request. Here it is twice, once with if and once with match:

def failed_path_if(event: object) -> str | None:
    if not isinstance(event, dict) or event.get("kind") != "http":
        return None
    status = event.get("status")
    path = event.get("path")
    if isinstance(status, int) and isinstance(path, str) and status >= 500:
        return path
    return None


def failed_path(event: object) -> str | None:
    match event:
        case {"kind": "http", "status": int() as status,
              "path": str() as path} if status >= 500:
            return path
        case _:
            return None

Both return the same result for any dictionary. The match version puts the keys, their types, the names it binds and the guard in one place. The if version spreads the same checks across two if statements and two lookups.

Use match when the branches are shapes of one value

match reads better when all three of these hold:

  • Every branch looks at the same value, the subject.
  • Each branch asks about its shape, such as its type, its length or its keys.
  • The branch body uses pieces of that value.

Parsing commands, routing events and simplifying a tree of nodes, as in the exercises before this article, all fit.

Use if and elif for unrelated conditions

if user.is_admin:
    allow()
elif quota_exceeded(user):
    reject("quota")
elif now > deadline:
    reject("late")

These conditions ask about different things, so there is no single subject. With match, every branch would be case _ if ...:, which is an elif chain with more words. Keep the if.

For a few plain values, such as a status that is "open", "closed" or "pending", the two read about the same. Pick the one your team reads faster.

When every branch calls a function with the same arguments, a dictionary of handlers can be shorter than either:

handlers = {"start": start_job, "stop": stop_job}
handler = handlers.get(command)
if handler is None:
    raise ValueError(f"unknown command: {command}")
handler(job_id)

Common mistakes

Comparing with a bare name. A bare name in a case captures the subject, so case PENDING: matches every value. To compare with a constant, write a dotted name such as Status.PENDING, or use if status == PENDING:.

Trusting a dotted name you never tested. Python looks up Status.PENDING only when it reaches that case. A typo such as Status.PENDNG raises AttributeError at that moment, not when the module loads. Write a test that reaches every case.

Treating a pattern as validation. A mapping pattern checks the keys you list and ignores the rest. That is fine for routing. Validation needs more, such as rejecting unknown keys, checking ranges and reporting clear errors. Write explicit checks for that, or use a validation library.

Check your understanding A function checks user.is_admin, then user.is_banned, then len(cart) > 10. Should you rewrite it with match? Show answer

No. The three conditions ask about different things, so there is no single subject to match on. Every case would be case _ if ...:, which is an elif chain with extra words. match is the better choice when the branches describe shapes of one value and use pieces of it.

Check your understanding Does match run faster than an if and elif chain? Show answer

No. Python tries the cases in order, from the top, and stops at the first case whose pattern and guard both succeed. That is the same kind of work an elif chain does. Choose match for how the code reads. If speed matters in a loop that runs often, time both versions with timeit.

step 14 of 23

Pattern Matching, f-Strings and Newer Syntax

Up next Reuse a Computed Value with the Walrus Operator