Skip to main content

How to Implement Switch-Case Alternatives in Python

For many years, Python did not include a traditional switch or case statement found in languages like Java, C, or JavaScript. Instead, Python developers relied on dictionary-based dispatch and if-elif-else chains to handle multi-branch logic. With Python 3.10, the language introduced structural pattern matching (match-case), which goes far beyond simple value switching.

In this guide, you will learn three approaches to implementing switch-case logic in Python: dictionary mapping, match-case pattern matching, and if-elif-else chains. Each method is explained with examples and output so you can choose the right tool for your situation.

Dictionary Mapping (The Classic Pythonic Approach)

Dictionaries are the most efficient way to emulate a switch statement in any Python version. They map "cases" (keys) directly to "actions" (values or functions), providing O(1) constant-time lookups regardless of how many cases exist:

def handle_error(code):
responses = {
200: "Success",
301: "Moved Permanently",
404: "Page Not Found",
500: "Internal Server Error",
}

# .get() provides a "default" case when the key is missing
return responses.get(code, "Unknown status code")

print(handle_error(404)) # Page Not Found
print(handle_error(200)) # Success
print(handle_error(999)) # Unknown status code

Output:

Page Not Found
Success
Unknown status code

The .get() method returns the value for the given key if it exists, or the default value ("Unknown status code") if it does not. This eliminates the need for a separate default branch.

Dispatching to Functions

For more complex logic, store function references as dictionary values instead of simple strings. This lets each case execute entirely different behavior:

def process_create(data):
print(f"Creating record: {data}")

def process_update(data):
print(f"Updating record: {data}")

def process_delete(data):
print(f"Deleting record: {data}")

def process_unknown(data):
print(f"Unknown action for: {data}")

def dispatch(action, data):
actions = {
"create": process_create,
"update": process_update,
"delete": process_delete,
}

# Look up the function, fall back to default
handler = actions.get(action, process_unknown)
handler(data)

dispatch("create", "user_123")
dispatch("delete", "user_456")
dispatch("archive", "user_789")

Output:

Creating record: user_123
Deleting record: user_456
Unknown action for: user_789
tip

Dictionary dispatch is especially powerful in plugin systems, command routers, and event handlers where the set of actions can be extended at runtime by simply adding new entries to the dictionary.

Structural Pattern Matching (match-case, Python 3.10+)

Python 3.10 introduced match-case, which is far more powerful than a traditional switch statement. It can match literal values, combine patterns, destructure sequences, and inspect object attributes:

Simple Value Matching

def check_status(status):
match status:
case 200:
return "OK"
case 301:
return "Moved Permanently"
case 404 | 405: # Combine multiple values with |
return "Client Error"
case _: # The wildcard acts as the default case
return "Unknown"

print(check_status(200)) # OK
print(check_status(404)) # Client Error
print(check_status(999)) # Unknown

Output:

OK
Client Error
Unknown

The _ wildcard matches anything and serves as the default case. The | operator lets you handle multiple values with the same branch.

Destructuring Sequences and Objects

The real power of match-case is structural pattern matching, which can unpack and inspect data structures:

def process_command(command):
match command.split():
case ["quit"]:
return "Exiting..."
case ["greet", name]:
return f"Hello, {name}!"
case ["move", x, y]:
return f"Moving to ({x}, {y})"
case _:
return "Unknown command"

print(process_command("quit"))
print(process_command("greet Alice"))
print(process_command("move 10 20"))
print(process_command("fly"))

Output:

Exiting...
Hello, Alice!
Moving to (10, 20)
Unknown command

The match statement splits the string and matches the resulting list against each pattern. Variables like name, x, and y are automatically bound to the corresponding elements.

Adding Guard Conditions

You can add if guards to patterns for additional filtering:

def classify_number(n):
match n:
case x if x < 0:
return "Negative"
case 0:
return "Zero"
case x if x > 100:
return "Large positive"
case _:
return "Small positive"

print(classify_number(-5))
print(classify_number(0))
print(classify_number(42))
print(classify_number(200))

Output:

Negative
Zero
Small positive
Large positive

Using if-elif-else Chains

For range checks, complex boolean conditions, or any logic that does not map cleanly to exact value matching, the traditional if-elif-else chain remains the best choice:

def classify_score(score):
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
elif score >= 60:
return "D"
else:
return "F"

print(classify_score(95))
print(classify_score(73))
print(classify_score(45))

Output:

A
C
F

Neither dictionary mapping nor match-case handle range-based comparisons naturally. For this type of logic, if-elif-else is the clearest and most appropriate tool.

A Common Mistake: Using a Dictionary for Range Checks

A frequent anti-pattern is trying to force dictionary mapping onto problems that involve ranges or conditions:

# Wrong: only works for exact values, not ranges
grades = {90: "A", 80: "B", 70: "C", 60: "D"}

score = 85
print(grades.get(score, "F")) # Returns "F" because 85 is not a key

Output:

F

The score 85 should map to "B", but the dictionary only matches exact keys. Use if-elif-else for range-based logic instead.

When to Use Which

RequirementRecommended MethodPython Version
Simple value-to-result lookupDictionary mappingAll versions
Value-to-function dispatchDictionary with function valuesAll versions
Range checks (e.g., score >= 90)if-elif-elseAll versions
Multiple values for one casematch-case with |3.10+
Destructuring lists, tuples, objectsmatch-case3.10+
Complex boolean conditionsif-elif-elseAll versions
Performance Consideration

Dictionary lookups run in O(1) constant time, meaning performance is the same whether there are 5 cases or 500. Long if-elif-else chains run in O(n) time because Python evaluates each condition sequentially until it finds a match. For simple value matching with many cases, dictionary dispatch is measurably faster.

Conclusion

Python offers three effective alternatives to traditional switch-case statements. Use dictionary mapping for simple value lookups and function dispatch, as it works in all Python versions and provides O(1) performance.

  • Use match-case (Python 3.10+) when you need structural pattern matching, sequence destructuring, or combining multiple patterns in a single branch.
  • Use if-elif-else for range-based comparisons and complex boolean conditions where exact value matching does not apply.

By choosing the right approach for each situation, you can replace messy conditional ladders with clean, efficient, and readable code.