Python or Condition: How the or Operator Works
python or condition: Learn how Python's or operator returns operands rather than booleans, how short-circuiting works, and when to use explicit None checks instead.
Python's or operator does not always produce True or False. When you evaluate a or b, Python returns a if a is truthy, and b otherwise. That single rule drives the python or condition idiom, and it explains behavior that surprises developers coming from languages where logical operators are strictly boolean.
What or Actually Returns
or evaluates its left operand first. If the left operand is truthy, the expression returns the left operand's value without looking at the right side. If the left operand is falsy, the expression returns the right operand's value.
result = 0 or 42 print(result) # 42 result = "hello" or 42 print(result) # "hello"
In the first example, 0 is falsy, so or returns 42. In the second, "hello" is truthy, so the expression returns "hello" and never evaluates 42.
The same rule applies inside an if statement. Python converts the returned value to a boolean only when it needs to decide which branch to take.
value = "" if value or "default": print("branch taken")
Here value is an empty string, which is falsy, so or returns "default". Since "default" is truthy, the branch runs.
Short-Circuit Evaluation and Its Effect on Conditions
or short-circuits. If the left operand is truthy, Python does not evaluate the right operand at all. This matters when the right side has side effects or raises exceptions.
def fetch_user(): print("fetch_user called") return {"name": "Ada"} user = {"name": "Grace"} or fetch_user()
Because the dictionary on the left is truthy, fetch_user is never called. The print statement inside it never runs.
This behavior is useful for fallback logic, but it also means you cannot rely on the right side executing when the left side is truthy. If the right side is a function call that must always run, or is the wrong tool.
Using or in if Statements
In a plain conditional, or combines multiple conditions and the result is used as a boolean.
def can_access(user, resource): if user.is_admin or resource.is_public: return True return False
The expression user.is_admin or resource.is_public returns either user.is_admin (if truthy) or resource.is_public (if falsy). Python then converts that returned value to a boolean for the if. The visible behavior is the same as a strictly boolean operator, which is why the distinction is easy to miss.
The short-circuit rule applies here too. If user.is_admin is truthy, resource.is_public is never evaluated. That can hide bugs if resource.is_public is a property that raises an exception under certain conditions, or if it has side effects.
The a or b Pattern for Default Values
Because or returns an operand rather than a boolean, it is commonly used to pick a default value.
name = input_name or "anonymous"
If input_name is an empty string, which is falsy, the expression evaluates to "anonymous". If input_name contains text, the expression evaluates to that text.
This pattern works for None, empty strings, empty lists, empty dictionaries, and 0. It fails when the left value is meaningful but falsy. A score of 0 is a valid value in many applications, yet score or 10 would replace it with 10.
score = 0 display_score = score or 10 # 10, not 0
For that reason, when the left side can legitimately be 0, False, or an empty collection, prefer an explicit None check:
display_score = score if score is not None else 10
The is not None check preserves 0 and other falsy values while still replacing None.
Precedence: Mixing or with and and Comparisons
and binds more tightly than or. The expression a or b and c parses as a or (b and c), not (a or b) and c.
result = True or False and False print(result) # True
Because and evaluates first, False and False is False, and True or False is True. If you intended (True or False) and False, the result would be False. Parentheses remove the ambiguity and make the intent explicit.
Comparisons bind more tightly than both and and or, so x > 0 or x < -10 parses as (x > 0) or (x < -10) without extra parentheses. That is usually what you want, but adding parentheses can improve readability in complex conditions.
Performance and Runtime Cost of Short-Circuiting
The main performance characteristic of or is that it avoids evaluating the right operand when the left operand is truthy. In a condition like is_cached or compute_result(), the expensive compute_result() call runs only when is_cached is falsy.
The reverse is also true: if the left operand is usually falsy, the right operand is evaluated on every call. Ordering operands so that the cheaper or more likely truthy check comes first reduces unnecessary work.
if user and user.session and user.session.is_valid(): ...
Here user is checked first. If user is None, the remaining operands are never evaluated, which also prevents an AttributeError on user.session. This pattern relies on short-circuiting for correctness, not just performance.
No benchmark numbers are needed to apply this rule: the cost is proportional to how often the right side executes and how expensive it is.
Edge Cases and When to Avoid or
The or operator is not a substitute for a ternary or a null-coalescing operator in every situation. Three cases commonly cause problems.
First, when the left value is falsy but valid, as with 0, "", or [], or silently replaces it. Use an explicit None check instead.
Second, when the right operand is a function call that must run regardless of the left value, or will skip it when the left side is truthy. Use a regular conditional or an explicit if in that case.
Third, when combining many conditions, the returned value of or is the first truthy operand, which can be surprising if you later use the result as data rather than as a boolean.
priority = user.role or guest_role or "default"
This chains three fallbacks and returns the first truthy value. It is readable when the intent is exactly that, but it becomes hard to follow when the operands are complex expressions. In that case, an explicit conditional is clearer.
if user.role: priority = user.role elif guest_role: priority = guest_role else: priority = "default"
The explicit version makes the precedence and fallback order obvious, and it makes it easy to add logging or other logic at each step.