Truthiness of containers and falsy values
The gotcha
bool([]) is False. bool([0]) is True. bool([False]) is True. The container’s truthiness depends on whether it’s empty, not on what’s inside.
Minimal repro
bool([]) # False — empty list
bool([0]) # True — one element, even though that element is falsy
bool([False]) # True — same
bool([None]) # True — same
bool([[]]) # True — list containing an empty list
bool({}) # False — empty dict
bool({0: 0}) # True — non-empty
bool(set()) # False
bool({0}) # True
bool("") # False
bool("0") # True — non-empty string, even if it looks like zero
bool(" ") # True — whitespace is non-empty
bool(()) # False — empty tuple
bool((0,)) # True
The full list of falsy values
In Python, only these are falsy:
NoneFalse- Numeric zeros:
0,0.0,0j,Decimal(0),Fraction(0, 1) - Empty sequences:
"",(),[],range(0),bytes() - Empty mappings:
{} - Empty sets:
set(),frozenset() - Custom objects whose
__bool__returnsFalse, or whose__len__returns0(with no__bool__)
Everything else is truthy.
How bool() decides
For a custom object:
- Call
__bool__()if defined → must returnbool. - Else call
__len__()if defined → 0 is False, anything else True. - Else default to
True(every object is truthy by default).
class Empty:
def __bool__(self):
return False
bool(Empty()) # False
class Box:
def __init__(self, items): self.items = items
def __len__(self): return len(self.items)
bool(Box([])) # False — falls through __bool__, uses __len__
bool(Box([0])) # True — __len__ returns 1
The if items vs if items is not None bug
The classic bug:
def get_users(filter=None):
if filter: # falsy if {} or [] passed in
return User.query.filter_by(**filter).all()
return User.query.all()
get_users(filter={}) # falls through — but caller probably meant "no filters"
If callers might pass an empty dict or list to mean “this is the value, just empty,” use explicit None checks:
def get_users(filter=None):
if filter is not None: # treats {} as a real value
return User.query.filter_by(**filter).all()
return User.query.all()
The same trap with optional integer arguments:
def paginate(items, limit=None):
if limit: # limit=0 is falsy → no limit applied
items = items[:limit]
limit=0 should mean “return nothing.” if limit: treats it as “no limit set.” Use if limit is not None:.
DataFrames and arrays — bool() is forbidden
import numpy as np
arr = np.array([1, 2, 3])
if arr: # ValueError: ambiguous
...
NumPy and pandas raise on truthiness of multi-element arrays — which element should determine truthiness? Use arr.any(), arr.all(), or len(arr) > 0.
Pandas DataFrames same: if df: raises. Use df.empty.
“Truthy short-circuit” patterns
name = user_input or "default" # uses "default" if user_input is "" or None
items = config.get("items") or [] # treats missing OR empty list the same way
These are idiomatic but conflate “missing” with “empty/zero.” Use them when the conflation is intentional.
# Intentional: any falsy value gets replaced
return user.display_name or user.email or "anonymous"
# Bug-prone: 0 quantity becomes "no quantity"
quantity = parsed.get("qty") or 1 # qty=0 → silently becomes 1
For the bug-prone case, use dict.get with a default, or check explicitly.
Boolean operators don’t return bool
and and or return one of their operands, not True/False:
1 or 2 # 1 — first truthy
0 or 2 # 2 — first truthy
1 and 2 # 2 — last truthy when all truthy
0 and 2 # 0 — first falsy
"" or "a" # "a"
[] or [0] # [0]
Only not returns a bool. This is what makes x or default an idiom — but also why subtle bugs hide.
Comparing with True / False directly
if response == True: # almost never what you want
True == 1 is True (because bool is a subclass of int), so response == True matches both True and 1. Use if response: for truthiness, if response is True: for identity.
The == against True / False literal is also flagged by linters (E712 in pycodestyle/ruff).
Interview angle
- Q: “Is
bool([])true or false? What aboutbool([0])?” — False, True. Container truthiness is emptiness. - Q: “When would
if x:give the wrong answer?” — when0,"",[],{}are valid values distinct from “missing.” - Follow-up: “How does Python decide truthiness for a custom class?” —
__bool__first, then__len__, default True. - Follow-up: “What does
[] or [0]return?” —[0].orreturns the first truthy operand, not a bool.
See 04_bool_is_int_subclass.md, 16_dict_get_falsy_default.md, 17_floats_and_equality.md.