Python or Operator: How It Really Works
python or operator: Learn how Python's or operator returns operands rather than booleans, how truthiness drives the result, and where the pattern breaks with falsy val...
python or operator requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The or operator in Python does not return a boolean. It returns one of its operands. When you write a or b, Python evaluates a; if a is truthy, the expression evaluates to a and b is never evaluated. If a is falsy, the expression evaluates to b. This behavior is the foundation of several common Python patterns, and it is also the source of a few surprising bugs.
What Python's or Operator Actually Returns
Consider this expression:
result = 0 or 42 print(result) # 42
The result is 42, not True. Both operands are kept as their original values. The same rule applies when the left operand is truthy:
result = "hello" or "world" print(result) # "hello"
Because or returns an operand rather than a boolean, you can use it directly in assignments, return statements, and function arguments without converting the result. The expression a or b is equivalent to a conditional expression:
result = a if a else b
The conditional form is more verbose but makes the fallback logic explicit. Use it when the reader of the code is unlikely to know that or returns an operand.
Truthiness Determines the Result
Python decides whether a is used by checking its truthiness, not by checking whether it is True or False. The following values are falsy:
NoneFalse- numeric zero:
0,0.0,0j - empty sequences and collections:
"",[],(),{},set() range(0)
Everything else is truthy, including non-empty strings, non-zero numbers, and objects whose class does not define __bool__ or __len__. A custom class can change this behavior by defining __bool__:
class Config: def __init__(self, enabled): self.enabled = enabled def __bool__(self): return self.enabled config = Config(False) selected = config or "default" print(selected) # "default"
When a class defines __bool__, that method controls how the instance behaves in or expressions. If the class defines __len__ instead, Python falls back to checking whether len(obj) is zero.
Using or for Default Values
The most common production use of or is supplying a default when a value may be missing:
name = user_input or "anonymous"
This works cleanly when the left operand is None or an empty string. The pattern is compact and idiomatic, and it avoids an explicit if statement in many cases. The same pattern appears in function arguments:
def connect(timeout=None): timeout = timeout or 30 ...
The fallback applies whenever the caller passes None, 0, or any other falsy value. That convenience is also the source of the main limitation, which is covered below.
Short-Circuit Evaluation and Its Side Effects
Because or stops evaluating as soon as the left operand is truthy, the right operand may never run. This is called short-circuit evaluation, and it matters when the right operand has side effects:
def fetch_data(): print("fetching data") return [1, 2, 3] cached = [4, 5, 6] result = cached or fetch_data() # fetch_data() is never called
The function call on the right side is skipped entirely when the left side is truthy. This is useful for lazy initialization, but it also means you cannot rely on the right operand running. If the right operand must always execute, evaluate it before the or expression:
fallback = fetch_data() result = cached or fallback
Short-circuiting also affects exception handling. If the left operand raises an exception, the right operand never runs; if the left operand is truthy, the right operand is never evaluated, so any error it would raise is avoided.
Common Mistakes With Falsy Values
The operand-returning behavior becomes a bug when a falsy value is a legitimate result. Consider a function that returns a count:
def item_count(): return 0 count = item_count() or 10 print(count) # 10, even though the real count is 0
The same problem occurs with empty strings, empty lists, and False itself:
def is_admin(user): return False admin = is_admin(user) or "guest" print(admin) # "guest"
When 0, "", [], or False are valid outcomes, or silently replaces them with the fallback. The fix is to check for None explicitly instead of relying on truthiness:
count = item_count() if count is None: count = 10
A clearer approach is an explicit conditional that preserves 0, "", and False as valid results while still supplying a default for missing values:
count = item_count() count = count if count is not None else 10
The explicit is not None check avoids the truthiness trap entirely.
or vs |: Two Different Operators
The or keyword is frequently confused with the | operator, which is a different operation entirely. For integers, | performs bitwise OR:
a = 0b1100 b = 0b1010 print(a | b) # 14 (0b1110)
For sets, | returns the union:
left = {1, 2, 3} right = {3, 4, 5} print(left | right) # {1, 2, 3, 4, 5}
For booleans, | performs a non-short-circuit OR: both operands are always evaluated, and the result is a boolean. or short-circuits and returns an operand. The two operators are not interchangeable, and using | where or was intended usually produces a TypeError or a surprising value.
Chaining and Operator Precedence
When or appears alongside and and not, precedence determines how the expression is grouped. not binds tightest, then and, then or:
result = not a or b and c # Equivalent to: (not a) or (b and c)
The expression b and c is evaluated first, and its result becomes the right operand of the or. If you need a different grouping, use parentheses:
result = (not a or b) and c
Chained or expressions evaluate left to right and return the first truthy operand:
value = first() or second() or third() or "default"
This is a common way to try several fallbacks in order. Each function is called only until one returns a truthy value, which keeps the pattern efficient when the earlier calls are cheap.
Runtime Cost and When to Avoid or
The or operator itself has negligible runtime cost: it is a single bytecode operation (JUMP_IF_TRUE_OR_POP in CPython) followed by a stack adjustment. The real cost is in the operands. Because the right operand is skipped when the left is truthy, or can actually reduce work compared to an explicit if that evaluates both sides. The performance concern is not the operator but the expression on the left. If the left operand is an expensive function call, that call runs regardless of the outcome:
result = expensive_lookup() or "fallback"
The lookup runs every time. If the lookup is the dominant cost, the or pattern does not help; you would need to cache the lookup result separately. In practice, or is rarely a performance bottleneck. The more important consideration is correctness: the pattern is safe when the left operand is None or empty, and unsafe when falsy values are valid results. Choosing between or and an explicit if should be driven by whether the truthiness semantics match the data, not by micro-optimization.