Short-Circuit Evaluation
After this topic, you will be able to:
Understand how Python's and/or operators work internally, use short-circuit evaluation in practical patterns, and explain why order matters.
What is Short-Circuit Evaluation?
Short-circuit evaluation is a logical operator optimization where the evaluation of an expression stops as soon as the result is known.
# and: if the first operand is False, the second is not evaluatedFalse and print("This never runs") # False
# or: if the first operand is True, the second is not evaluatedTrue or print("This never runs") # TrueWhy does it work this way? Logically, it makes sense:
and: If any part is False, the whole thing is False. If the first part is False, there's no need to check the second.or: If any part is True, the whole thing is True. If the first part is True, there's no need to check the second.
How and Works
# and returns the first falsy value, or the last valueprint(0 and 5) # 0 (0 is falsy β stops here)print("" and "hello") # "" (empty string is falsy)print(None and 42) # None (None is falsy)
print(1 and 5) # 5 (1 is truthy β goes to next β returns 5)print("hello" and 42) # 42 (both truthy β returns the last value)print(3 and 2 and 1) # 1 (all truthy β returns the last value)print(3 and 0 and 1) # 0 (stops at 0)Key takeaway: and returns the first falsy value. If all values are truthy, it returns the last value.
Python's Falsy Values
# These all evaluate to Falsebool(False) # Falsebool(0) # Falsebool(0.0) # Falsebool("") # Falsebool(None) # Falsebool([]) # False (empty list)bool({}) # False (empty dictionary)
# Everything else is Truebool(1) # Truebool("hello") # Truebool([1, 2]) # TrueHow or Works
# or returns the first truthy value, or the last valueprint(0 or 5) # 5 (0 is falsy β goes to next β returns 5)print("" or "hello") # "hello" (empty string is falsy β returns "hello")print(None or 42) # 42
print(1 or 5) # 1 (1 is truthy β stops here)print("hi" or "bye") # "hi" (first is truthy)print(0 or "" or None) # None (all falsy β returns the last value)print(0 or "" or 42) # 42 (first truthy value)Key takeaway: or returns the first truthy value. If all values are falsy, it returns the last value.
Practical Pattern: Setting Defaults
# If the user doesn't enter a name, use "Guest"username = input("Enter name: ") or "Guest"print(f"Hello, {username}")
# Set a default value if the configuration is missingconfig = {}db_host = config.get("host") or "localhost"db_port = config.get("port") or 5432This is a pattern for setting default values with or. If the left side is falsy (empty string, None, etc.), the right side is used. This is a common pattern in JavaScript as well.
Caution: This pattern can be dangerous if 0 or an empty string is a valid value.
# Bug: 0 is also falsy, so the default is appliedcount = 0result = count or 10 # 10 (should be 0)
# Fix: Explicit comparisonresult = count if count is not None else 10 # 0Practical Pattern: Conditional Execution
# Use `and` for conditional execution (alternative to `if`)data = [1, 2, 3]data and print(f"Data has {len(data)} items") # Prints
empty = []empty and print("This won't print") # Doesn't print (empty list = falsy)# Safe attribute accessuser = {"name": "Hoon", "address": None}
# If `address` is None, don't call `.get()`city = user.get("address") and user["address"].get("city")print(city) # None (and stops at None)Why Order Matters
The key to short-circuit evaluation is to place expensive operations later.
def is_valid_user(user_id): """DB lookup β slow operation""" print(f"Querying DB for user {user_id}...") return user_id > 0
def has_permission(user_id, action): """Permission check β even slower operation""" print(f"Checking permission for {action}...") return True
# Good: Checks the faster condition firstuser_id = -1if user_id > 0 and is_valid_user(user_id) and has_permission(user_id, "read"): print("Access granted")# Output: (Nothing printed β `user_id > 0` is False, so the DB is not queried)
# Bad: Checks the slower condition firstif is_valid_user(user_id) and user_id > 0: print("Access granted")# Output: "Querying DB for user -1..." (Unnecessary DB query)It's also useful for preventing errors:
# Indexing an empty list causes an erroritems = []
# Bad: Causes an IndexError# if items[0] > 10:
# Good: Checks if the list is empty firstif items and items[0] > 10: print("First item is large")# `items` is falsy (empty list), so `items[0]` is not executedThings to Keep in Mind When Debugging
Short-circuit evaluation can make debugging harder.
def check_permission(user): print(f"Checking permission for {user['name']}") return user.get("role") == "admin"
def validate_input(data): print(f"Validating: {data}") return len(data) > 0
user = {"name": "Hoon", "role": "admin"}data = "hello"
if check_permission(user) or validate_input(data): print("Granted")
# Output:# Checking permission for Hoon# Granted# β `validate_input` is not executed! (print statement is skipped)In an or statement, if the first part is True, the second function is not even called. If you have a function with side effects (logging, incrementing a counter) in a conditional, its execution depends on the result of the first part. If the side effect needs to always run, call it outside the conditional.
Comparison with Other Languages
// JavaScript β same behavior
const name = userInput || "Guest";
const port = config.port || 3000;
// JavaScript ES2020: ?? (Nullish Coalescing)
const count = 0 ?? 10; // 0 (?? only checks for null/undefined)
const count2 = 0 || 10; // 10 (|| checks for all falsy values)# Python doesn't have the ?? operator# Instead, use explicit comparisoncount = 0result = count if count is not None else 10 # 0JavaScript's ?? (Nullish Coalescing) only checks for null and undefined, so it's safe when 0 or an empty string is a valid value. Python doesn't have a corresponding operator, so you need to use an explicit is not None comparison.
Combining with not
# `not` doesn't use short-circuiting; it's a simple inversionprint(not True) # Falseprint(not 0) # Trueprint(not "") # Trueprint(not "hello") # False# Precedence in complex conditions# not > and > orprint(not True or False) # False (not True β False, False or False β False)print(not (True or False)) # False (True or False β True, not True β False)
# Use parentheses explicitly for readabilityif (age >= 18) and (not is_banned): print("Access allowed")Key Takeaways
| Operator | Short-Circuit Condition | Return Value |
|---|---|---|
and | First operand is falsy | First falsy value, or the last value |
or | First operand is truthy | First truthy value, or the last value |
not | (No short-circuiting) | Always True or False |
Understanding short-circuit evaluation gives you three benefits: setting default values (x or default), safe attribute access (items and items[0]), and performance optimization (putting faster conditions first). Most programming languages (JavaScript, Java, C++) follow the same rules, so once you learn it, you can apply it everywhere.