Boolean and Truthiness
True, False, what counts as falsy, and short-circuit evaluation.
What you will be able to do
- Use True and False, and convert any value with bool()
- Recite the falsy values, and treat everything else as truthy
- Write `if value:` where it is right, and an explicit comparison where it is not
- Tell None apart from False, from 0, and from an empty collection
- Use any() and all(), including what they do with an empty iterable
- Explain how an object decides its own truthiness
The idea, in plain English
Python has two boolean values, True and False, and a type called bool. Comparisons produce them, if statements consume them, and so far nothing is surprising.
What is particular to Python is that an if statement does not require a bool. Give it any value at all and Python decides whether that value should count as true. A non-empty string counts as true; an empty one counts as false. This is called truthiness, and it is why so much Python reads as `if users:` rather than `if len(users) > 0:`.
The rule is easier than it sounds because the falsy list is short and closed. False, None, any zero, any empty collection - that is essentially all of it. Everything else in the language is truthy, including the string "False" and the list [0].
The trap is treating truthy as a synonym for valid. An age of 0 is falsy, an empty search result is falsy, and a status of "inactive" is truthy. Knowing when to use truthiness and when to be explicit is most of what this lesson is for.
Worked example: Validating a signup form where an age of 0 must still count.
The falsy list is short
Rather than learning what is true, learn the handful of things that are false. Everything else - every non-empty string, every non-zero number, every non-empty container, every object without an opinion - is truthy.
Two that catch people: " " is truthy because a space is a character, and [0] is truthy because the list has something in it even though that something is falsy. Truthiness of a container is about emptiness, never about its contents.
FalseThe boolean itself.NoneThe absence of a value.0, 0.0, 0jZero in every numeric type. Also Decimal("0") and Fraction(0).""The empty string. Note that " " is truthy - it holds a space.[] () {} set()Empty list, tuple, dict, set. Emptiness is what matters, not contents.range(0)An empty range.Everything elseTruthy. Including "False", "0", [0], [[]], and -1.Watch out: bool("False") is True and bool("0") is True. They are non-empty strings. Converting text from a form needs a real parse, not bool().
Truthy is not the same as valid
`if value:` asks "is there something here?". It does not ask "is this correct?", and confusing the two produces bugs that only appear for particular inputs.
Three places it goes wrong. A quantity of 0 or a price of 0 is falsy, so `if not quantity:` treats a deliberate zero as missing. A username of " " is truthy, because spaces are characters - strip it first. And a status of "inactive" is truthy, so `if status:` is True for every status there is; you wanted `if status == "active":`.
The rule of thumb: use truthiness for presence and emptiness, and an explicit comparison for anything about the specific value.
if users:Right. "Is the list non-empty?" is exactly a truthiness question.if name.strip():Right for user input - " " is truthy until you strip it.if not score:Wrong when 0 is a real score. Matches 0, None, "", and [] alike.if score is None:Right when you mean "no score was supplied".if status:Wrong - every non-empty status is truthy, including "inactive".if status == "active":Right. The question is about a specific value.any() and all(), including the empty case
any() is true when at least one item is truthy; all() is true when every item is. Both take any iterable, and both short-circuit - any() stops at the first truthy item, all() at the first falsy one.
The surprise is what they do with nothing. any([]) is False, which people expect. all([]) is True, which almost nobody does. It is called vacuous truth: there is no item that fails, so the claim "all of them pass" holds.
That matters in validation. `if all(field_is_valid(f) for f in fields):` passes when fields is empty, so a form with nothing in it is reported as valid. Check that the iterable is non-empty first, or the empty case will get through.
any([False, False, True])True - at least one is truthy. Stops as soon as it finds one.any([False, False])False - none are truthy.any([])False - there is nothing that could be true.all([True, True])True - none fail.all([True, False])False - stops at the first falsy item.all([])True - vacuously. Nothing failed, because there was nothing.Watch out: all([]) being True is the source of a real validation bug. If an empty collection should fail, test for it: `if fields and all(...)`.
How an object decides its own truthiness
When Python needs a truth value it asks the object, in a fixed order. If the class defines __bool__, that answer is used. Otherwise, if it defines __len__, the object is falsy when its length is zero. If neither exists, the object is truthy.
That last rule is why every ordinary object you create is truthy by default, and why an empty list is falsy without anyone writing bool logic for it - list defines __len__.
You will define these yourself once you reach classes. The useful thing now is knowing that `if thing:` may run code the class author wrote, which is worth remembering when a truth test behaves unexpectedly.
Syntax and examples
print(bool(True), bool(False)) # True False
print(bool(1), bool(0), bool(-1)) # True False True
print(bool(0.0), bool(0j)) # False False
print(bool("hello"), bool("")) # True False
print(bool(" ")) # True <- a space is a character
print(bool([1]), bool([])) # True False
print(bool([0])) # True <- non-empty, contents irrelevant
print(bool({}), bool({"a": 1})) # False True
print(bool(None)) # False
print(bool("False"), bool("0")) # True True <- non-empty stringsname = "Chandu"
if name:
print("Name provided") # runs
courses = []
if not courses:
print("No courses yet") # runs
# The whitespace trap, and the fix
username = " "
if username:
print("looks provided") # runs - and should not
if username.strip():
print("really provided") # does not run - correctprint(bool(None), bool(False), bool(0), bool([])) # all False
# ...but they mean different things
score = None # no score has been recorded
score = 0 # the student scored zero
verified = None # we have not checked yet
verified = False # we checked, and they are not verified
# So the check has to match the meaning
score = 0
if not score:
print("treated as missing") # runs - wrong, 0 is a real score
if score is None:
print("genuinely missing") # does not run - correctprint(any([False, False, True])) # True
print(all([True, True, False])) # False
scores = [80, 40, 30]
print(any(score > 70 for score in scores)) # True
print(all(score > 20 for score in scores)) # True
# The empty case
print(any([])) # False - expected
print(all([])) # True - vacuous truth, and a real source of bugs
fields = []
print(all(f for f in fields)) # True - an empty form "passes"
print(bool(fields) and all(fields)) # False - guard the empty casevalue = 2
if value:
print("truthy") # runs
if value == True:
print("equals True") # does NOT run - 2 == True is False
# Because True is 1, only 1 compares equal to it
print(1 == True, 2 == True) # True False
# So write the plain test
is_active = True
if is_active: print("active")
if not is_active: print("inactive")class Course:
def __init__(self, lessons):
self.lessons = lessons
# Asked first when Python needs a truth value
def __bool__(self):
return len(self.lessons) > 0
print(bool(Course([]))) # False
print(bool(Course(["Python"]))) # True
class Basket:
def __init__(self, items):
self.items = items
# No __bool__, so Python falls back to __len__
def __len__(self):
return len(self.items)
print(bool(Basket([]))) # False
print(bool(Basket([1, 2]))) # True
class Plain:
pass
print(bool(Plain())) # True - neither method, so truthyform = {"name": "Chandu", "email": "chandu@example.com", "age": 0}
required = ("name", "email", "age")
# get() returns None for a missing key, which is what we test for -
# an age of 0 is supplied, even though it is falsy.
missing = [field for field in required if form.get(field) is None]
# Text fields also need to be non-blank, which truthiness alone will not catch
blank = [
field
for field in ("name", "email")
if isinstance(form.get(field), str) and not form[field].strip()
]
if missing or blank:
print(f"Missing: {missing} Blank: {blank}")
else:
print("Registration data is valid") # runs - age 0 is acceptedTip: Name booleans so the condition reads as English: is_active, has_permission, can_edit, should_retry. `if can_edit:` needs no comment.
Falsy values, in full
This list is closed. Anything not on it is truthy, including objects of your own classes unless they say otherwise.
FalseThe boolean.
bool(False) # False
NoneThe absence of a value.
bool(None) # False
0 0.0 0jZero, in any numeric type.
bool(0.0) # False
""The empty string only. " " is truthy.
bool("") # False[] () {} set()Empty list, tuple, dict, set.
bool([]) # False
range(0)An empty range.
bool(range(0)) # False
len(obj) == 0Any object whose __len__ returns zero.
bool(MyEmpty()) # False
The functions involved
bool(x)Converts any value to True or False using the rules above.
bool("") # Falseany(iterable)True if at least one item is truthy. False for an empty iterable. Short-circuits.
any([0, 1]) # True
all(iterable)True if every item is truthy - and True for an empty iterable. Short-circuits.
all([]) # True
not xAlways returns a real bool, unlike and and or.
not "" # True
isinstance(x, bool)Whether x is genuinely a bool rather than merely truthy.
isinstance(1, bool) # False
__bool__ / __len__How a class defines its own truthiness. __bool__ wins; __len__ is the fallback.
def __bool__(self): ...
Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“print(bool(" "), bool([0]), bool("False"))”
“print(any([]), all([]))”
“print(bool(2), 2 == True, 1 == True)”
“print(0 or "default", "" or "default")”
What usually goes wrong
It matches 0, 0.0, "", [], and None alike. A quantity of zero, a price of zero, or a score of zero is then treated as missing.
✗ if not quantity:
quantity = 1✓ if quantity is None:
quantity = 1A string of spaces is truthy because spaces are characters. Anything typed by a person needs stripping before the test.
✗ if username:✓ if username.strip():`if status:` is True for "active", "inactive", "banned", and every other non-empty string. It only tells you a status exists.
✗ if status:
grant_access()✓ if status == "active":
grant_access()Redundant at best and wrong at worst: 2 is truthy but 2 == True is False, because True is 1. The bare test is both shorter and correct.
✗ if is_active == True:
if is_active == False:✓ if is_active:
if not is_active:An empty iterable passes every all() check, so a form with no fields validates successfully. Guard the empty case explicitly.
✗ if all(checks):
proceed()✓ if checks and all(checks):
proceed()bool("False") is True, because it is a non-empty string. Every string from a form, an environment variable, or a CSV is truthy unless it is empty.
✗ debug = bool(os.environ.get("DEBUG"))✓ debug = os.environ.get("DEBUG", "").lower() in ("1", "true", "yes")Both are falsy, but None means "nothing was supplied" and [] means "a list was supplied and it is empty". In an API those are different answers.
✗ if not results:
return "no query was run"✓ if results is None:
return "no query was run"
if not results:
return "query returned nothing"Best practices
- Use truthiness for presence and emptiness; use == for questions about a specific value.
- Write `if users:` rather than `if len(users) > 0:` and `if not users:` rather than `== []`.
- Use `is None` whenever None is genuinely different from empty or zero.
- Never write == True or == False.
- Strip user input before testing it for content.
- Guard all() against an empty iterable when empty should fail.
- Prefix boolean names with is_, has_, can_, or should_.
- Parenthesise a condition that mixes and with or, even where precedence already agrees.
Practice
Write these yourself before opening anything. Getting them wrong first is most of how this sticks.
Predict, then check: bool(0), bool(1), bool(""), bool("Python"), bool(" "), bool([0]).
Show solutionHide solution
print(bool(0)) # False
print(bool(1)) # True
print(bool("")) # False
print(bool("Python")) # True
print(bool(" ")) # True - a space is a character
print(bool([0])) # True - the list is not emptyGiven value = None, print "Value is missing" - but only for None, not for 0 or "".
Show hintHide hint
Truthiness cannot tell these apart.
Show solutionHide solution
value = None
if value is None:
print("Value is missing") # runs
for other in (0, "", []):
if other is None:
print("would not run for", repr(other))With username = "" and password = "secret", print "Login details provided" only when both have real content.
Show solutionHide solution
username = ""
password = "secret"
if username.strip() and password.strip():
print("Login details provided")
else:
print("Both fields are required") # runsGiven is_logged_in = True, has_subscription = False, is_public = True, allow access when logged in and either subscribed or the course is public.
Show hintHide hint
The parentheses are not optional here - and binds tighter than or.
Show solutionHide solution
is_logged_in = True
has_subscription = False
is_public = True
can_access = is_logged_in and (has_subscription or is_public)
print(can_access) # TrueUsing any(), check whether any score in [80, 40, 30] is above 70. Using all(), check whether every score is above 20.
Show solutionHide solution
scores = [80, 40, 30]
print(any(score > 70 for score in scores)) # True
print(all(score > 20 for score in scores)) # TrueShow that all([]) is True, then write a check that treats an empty list of requirements as a failure.
Show solutionHide solution
requirements = []
print(all(requirements)) # True - vacuously
print(bool(requirements) and all(requirements)) # False - what we wanted
if requirements and all(requirements):
print("ready")
else:
print("not ready") # runsCourse access checker
Decide whether a learner can open a course from five facts about them. The rules interact, so the order you test them in matters - and a blocked user is blocked whatever else is true.
- Deny a blocked user outright, before checking anything else
- Require the user to be logged in
- Allow a public course without a subscription; require one for a private course
- Require the prerequisite where the course has one
- Print the reason for a denial, not just False
- Run every case below and check each answer is what you intended
cases = [
# logged_in, blocked, subscribed, public, prereq_done
(True, False, True, False, True),
(True, False, False, True, True),
(True, False, False, False, True),
(True, True, True, True, True),
(False, False, True, True, True),
(True, False, True, False, False),
]
for logged_in, blocked, subscribed, public, prereq_done in cases:
...Show one solutionHide solution
def check_access(logged_in, blocked, subscribed, public, prereq_done):
# Deny first, in priority order, so each reason is specific.
if blocked:
return False, "account is blocked"
if not logged_in:
return False, "not signed in"
if not public and not subscribed:
return False, "subscription required"
if not prereq_done:
return False, "prerequisite not completed"
return True, "access granted"
cases = [
(True, False, True, False, True),
(True, False, False, True, True),
(True, False, False, False, True),
(True, True, True, True, True),
(False, False, True, True, True),
(True, False, True, False, False),
]
for case in cases:
allowed, reason = check_access(*case)
print(f"{str(allowed):<6} {reason}")
# True access granted
# True access granted
# False subscription required
# False account is blocked
# False not signed in
# False prerequisite not completedKey points
- bool is a type with two values, True and False, written with capitals.
- Any value can be used as a condition; Python decides whether it counts as true.
- The falsy values are False, None, any zero, any empty collection, and range(0). Everything else is truthy.
- " " is truthy because it contains a character; [0] is truthy because the list is not empty.
- bool("False") is True - every non-empty string is truthy.
- Truthy does not mean valid. An age of 0 is falsy and a status of "inactive" is truthy.
- Use `if value:` for presence, and == for questions about a specific value.
- Use `is None` when None means something different from empty or zero.
- Never write == True: 2 is truthy but 2 == True is False.
- any() is False for an empty iterable; all() is True for one.
- Both any() and all() short-circuit at the first decisive item.
- A class defines its truthiness with __bool__, or failing that __len__; otherwise objects are truthy.
Quick check before you move on
Interview questions
Explain truthiness, and why Python works this way.
Every object can be asked for a truth value, so conditions accept any value rather than only booleans. It exists to let conditions read as intent - `if users:` rather than `if len(users) != 0:` - and it generalises: one rule covers strings, collections, numbers, and your own classes. The cost is that presence and validity become easy to confuse, which is where the bugs are.
Why is all([]) True?
Vacuous truth. all() asks whether any item fails; with no items, none does, so it returns True. It is also what makes all() composable - all(a + b) equals all(a) and all(b), which only holds if the empty case is True. Practically it means validation over an empty collection passes, so the empty case needs its own check.
What is wrong with `value = user_input or default`?
It replaces every falsy value, not just the missing one. A deliberate 0, an empty string, or an empty list all get overridden by the default. When only None should trigger the fallback, write `default if user_input is None else user_input`.
How does Python determine whether an object is truthy?
It calls __bool__ if the type defines it, and uses that result, which must be a bool. Otherwise it calls __len__ and treats zero as falsy. If the type defines neither, the object is always truthy - which is why a plain instance of a custom class is truthy even when it feels empty.
When would you deliberately use `if x is not None:` rather than `if x:`?
Whenever a falsy value is a legitimate value - numbers that can be zero, strings that can be empty, collections that can be empty, and booleans where False is meaningful. Optional function parameters are the common case: `def f(retries=None)` then `retries = 3 if retries is None else retries` lets a caller genuinely request zero retries.
Why does `bool` subclass `int`, and where does that bite?
For historical reasons - booleans were added after integers were already used for truth values, and making bool a subclass kept old code working. So True == 1 and sum([True, True]) is 2, which is occasionally useful for counting. It bites when a dict is given both 1 and True as keys, since they hash equally and the second overwrites the first.
Quiz
- 1.
What are the falsy values in Python?
- 2.
Is " " truthy or falsy?
- 3.
What is the difference between None and False?
- 4.
What does all([]) return, and why?
- 5.
Why is `if is_active:` preferred over `if is_active == True:`?
- 6.
How does a class control whether its instances are truthy?
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned