
Understanding Python's Truthiness: How if Decides What's True
In Python you can write if items: instead of if len(items) > 0:, and if name: instead of if name != "":. The condition in an if doesn't have to be a boolean. Any object works, and Python decides on the spot whether it counts as true or false. That property is called truthiness.
It makes everyday code shorter and more readable, but it also hides a few traps. The classic one is a function that treats 0 the same as "no value given", silently replacing a perfectly valid zero with a default. Bugs like that are easy to write and hard to spot in review.
This post covers the exact rules Python uses to decide truthiness, which built-in values are falsy, what and, or, and not actually return, how to make your own classes truthy or falsy, and when you should write an explicit comparison instead of relying on truthiness.
The Rule: Everything Has a Truth Value
Whenever Python needs a boolean, in an if, while, elif, a conditional expression, or as an operand of and, or, and not, it evaluates the object's truth value. You can see that value directly with bool():
print(bool(0), bool(42), bool(""), bool("hi"))
False True False True
The rule is simple to state: an object is truthy unless it's one of a small set of "empty" or "zero" things. Python's documentation lists them:
- The constants
NoneandFalse. - Zero of any numeric type:
0,0.0,0j,Decimal(0),Fraction(0, 1). - Empty sequences and collections:
"",[],(),{},set(),range(0),b"".
Here's the full list checked in one loop:
from decimal import Decimal
from fractions import Fraction
values = [None, False, 0, 0.0, 0j, Decimal(0), Fraction(0, 1),
"", [], (), {}, set(), range(0), b""]
for v in values:
print(f"{v!r:>16} -> {bool(v)}")
None -> False
False -> False
0 -> False
0.0 -> False
0j -> False
Decimal('0') -> False
Fraction(0, 1) -> False
'' -> False
[] -> False
() -> False
{} -> False
set() -> False
range(0, 0) -> False
b'' -> False
Everything else is truthy by default, including some values that look false at first glance:
for v in ["0", " ", "False", [0], [[]], (None,), {"": None}, -1, 0.0001, float("nan")]:
print(f"{v!r:>16} -> {bool(v)}")
'0' -> True
' ' -> True
'False' -> True
[0] -> True
[[]] -> True
(None,) -> True
{'': None} -> True
-1 -> True
0.0001 -> True
nan -> True
A few of these are worth remembering:
"0"and"False"are truthy. They're non-empty strings. If you read"0"from a config file or form field and test it withif value:, it passes. Convert it to the right type first." "is truthy. A string of whitespace isn't empty. Useif value.strip():if blank input should count as missing.- A container of falsy things is truthy.
[0],[[]], and(None,)each have one element, so they're non-empty. nanis truthy. It's not equal to zero, so it doesn't fall into the "zero" category.
How Python Computes the Truth Value
Under the hood, bool(x) follows a short procedure:
- If the type defines
__bool__, call it. It must returnTrueorFalse. - Otherwise, if the type defines
__len__, call it. The object is falsy if the length is0. - Otherwise, the object is truthy.
That explains every case above. Numbers implement __bool__ (zero is false). Containers implement __len__ (empty is false). A plain object with neither method is always truthy:
class Plain:
pass
print(bool(Plain()))
True
Making Your Own Classes Truthy or Falsy
If your class represents a collection, implement __len__ and you get sensible truthiness for free:
class Cart:
def __init__(self, items: list[str]) -> None:
self.items = items
def __len__(self) -> int:
return len(self.items)
print(bool(Cart([])), bool(Cart(["mug"])))
False True
Now if cart: reads naturally as "if the cart has anything in it".
When truthiness should mean something other than size, implement __bool__ directly:
class Result:
def __init__(self, ok: bool) -> None:
self.ok = ok
def __bool__(self) -> bool:
return self.ok
print(bool(Result(False)))
False
If a class defines both, __bool__ wins:
class Both:
def __bool__(self) -> bool:
return True
def __len__(self) -> int:
return 0
print(bool(Both()))
True
__bool__ must return an actual bool. Returning 1 or another truthy value is an error:
class Bad:
def __bool__(self):
return 1
bool(Bad())
TypeError: __bool__ should return bool, returned int
Use custom __bool__ sparingly. It's a good fit when "false" has one obvious meaning for the type (an empty result, a failed status). If readers would have to look up the class to know what if obj: means, an explicit method or property like obj.is_valid is clearer.
and, or, and not Return Operands, Not Booleans
This surprises people coming from other languages: and and or don't return True or False. They return one of their operands.
x or yreturnsxifxis truthy, otherwisey.x and yreturnsxifxis falsy, otherwisey.- Both short-circuit: the right side is only evaluated if needed.
print(0 or "default")
print("" or None or [])
print("a" and "b")
print([] and "never")
print(None or 0)
default
[]
b
[]
0
Walk through them:
0 or "default":0is falsy, soorreturns the right side."" or None or []: every operand is falsy, so you get the last one,[]."a" and "b":"a"is truthy, soandreturns the right side.[] and "never":[]is falsy, soandstops and returns it."never"is never evaluated.
not is the exception. It always returns a real bool:
print(not [], not "x")
True False
Short-circuiting is useful for guarding expressions that would otherwise fail, such as if user and user.is_admin:. If user is None, the attribute access never runs.
The or Default Pattern
Because or returns operands, it's often used to supply a default:
def greet(name: str | None = None) -> str:
name = name or "friend"
return f"Hello, {name}"
print(greet(), greet(""), greet("Ada"))
Hello, friend Hello, friend Hello, Ada
Here treating an empty string the same as None is exactly what you want. But the pattern has a sharp edge, which brings us to the most common truthiness bug.
The Zero Trap: When Falsy Isn't "Missing"
Consider a function with an optional numeric argument:
def set_volume(level: int | None = None) -> int:
level = level or 50
return level
print(set_volume(0))
50
The caller asked for volume 0 and got 50. Because 0 is falsy, or treated it like a missing value. The same bug shows up with empty strings that are legitimate values, empty lists that mean "no filters", and False flags.
The fix is to test for the thing you actually mean, which is "was a value provided?":
def set_volume(level: int | None = None) -> int:
if level is None:
level = 50
return level
print(set_volume(0))
0
A good rule of thumb: if None means "not provided", test with is None, not with truthiness. Use truthiness when every falsy value genuinely means the same thing for your logic.
When to Use Truthiness and When to Be Explicit
PEP 8 recommends relying on truthiness for sequences: write if not seq: rather than if len(seq) == 0:. It also recommends comparing to None with is or is not, never ==. Here's how that plays out in practice:
| You want to check | Write | Avoid |
|---|---|---|
| A list/dict/string is non-empty | if items: | if len(items) > 0:, if items != []: |
| A value was provided | if value is not None: | if value: (fails for 0, "") |
| A boolean flag | if enabled: | if enabled == True:, if enabled is True: |
| A number is non-zero | if count != 0: or if count: | |
| A string has visible text | if text.strip(): | if text: (passes for " ") |
For numbers, both forms are fine; if count != 0: can read more clearly when zero has a specific meaning in your domain.
Don't Compare to True or False
if flag == True: is redundant at best. At worst it's wrong, because True is only equal to values that equal the number 1, so a truthy value like "yes" or [1] fails the comparison:
print(bool([]) == False, [] == False)
True False
[] is falsy, but it isn't equal to False. Equality and truthiness are different questions. Let the if do its job.
Watch for Values Where Truthiness Is Surprising
Some objects have truth values you might not expect:
-
Regex matches.
re.match()returnsNonewhen nothing matches and a match object otherwise. A match object is always truthy, even for a zero-length match:import re m = re.match(r"\d*", "abc") print(m, bool(m), m.group() == "")<re.Match object; span=(0, 0), match=''> True TrueSo
if m:checks "did it match", not "did it match something non-empty". -
datetime.time. In very old Python versions, midnight (time(0, 0)) was falsy. That was changed in Python 3.5; today everytimeobject is truthy. You may still see defensive code written around the old behavior. -
NumPy arrays and pandas objects. Calling
bool()on an array with more than one element raisesValueError("The truth value of an array with more than one element is ambiguous"), and pandas does the same for aSeries. Use.any(),.all(),.size, or.emptyto say what you mean. -
NotImplemented. Using it in a boolean context has emitted aDeprecationWarningsince Python 3.9, and Python 3.14 makes it aTypeError. You'd only hit this when writing comparison methods, but it's a sign that the language is getting stricter where truthiness is ambiguous.
Truthiness in Built-in Functions
Several built-ins lean on truthiness directly:
print(any([0, "", None, 3]), all([1, "x", []]), all([]), any([]))
True False True False
any(iterable)returnsTrueif at least one element is truthy.all(iterable)returnsTrueif every element is truthy.- For an empty iterable,
all()isTrueandany()isFalse. That follows from logic ("every element of nothing is truthy" is vacuously true), but it catches people when they validate a possibly empty list withall().
Filtering out falsy values is a common one-liner. Both forms below produce the same result:
values = [0, 1, "", "a", None]
print([x for x in values if x])
print(list(filter(None, values)))
[1, 'a']
[1, 'a']
filter(None, ...) uses each element's truth value as the test. Just be aware that it drops 0 and "" along with None. If you only want to remove None, write [x for x in values if x is not None]. Comprehension filters like this are covered in more depth in List, Dict, and Set Comprehensions in Python.
bool Is a Subclass of int
One last detail that explains some odd-looking results: True and False are integers.
print(True + True, isinstance(True, int))
2 True
This is why sum(x > 10 for x in numbers) counts how many items exceed 10, and why True == 1 is True. It's handy for counting, but it also means a function annotated to take an int will happily accept True. If that matters, check for bool explicitly before int, since isinstance(True, int) is True.
FAQ
Is if x: slower or faster than if x is not None:?
The difference is negligible in practice. Choose based on meaning, not speed.
Why doesn't Python just require booleans in if?
Truthiness lets you express the most common checks ("is this empty?", "did this return anything?") without ceremony. The cost is that you have to know the rules, which is what this post is for.
Should my class define __bool__?
Only if there's a single obvious meaning for "false". Container-like classes should define __len__ instead and get truthiness from it.
Conclusion
Python's if accepts any object and asks it for a truth value. None, False, numeric zeros, and empty containers are falsy; nearly everything else is truthy. Classes control their truth value through __bool__ or __len__, and and/or return one of their operands rather than a plain boolean.
Use truthiness for what it's good at: checking whether a collection or string is empty. When None means "no value", test it with is None so a valid 0, "", or False doesn't get swapped for a default. That one habit prevents the majority of truthiness bugs you'll ever run into. If you want a refresher on how objects and names behave more generally, see Python Variables and Mutability.


