← Back to Core Python
Lesson 9 · Operators and Control Flow

Boolean and Truthiness

True, False, what counts as falsy, and short-circuit evaluation.

Beginner30 min

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.

Everything falsy in Python
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.

Which check does the job
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 and all
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

bool() on everything
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 strings
Truthiness in a condition
name = "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 - correct
None, False, 0, and empty are four different things
print(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 - correct
any() and all()
print(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 case
Why == True is worse than it looks
value = 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")
Objects decide for themselves
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 truthy
A real validation, written carefully
form = {"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 accepted

Tip: 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.

False

The boolean.

bool(False)   # False
None

The absence of a value.

bool(None)    # False
0 0.0 0j

Zero, 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) == 0

Any 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("")   # False
any(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 x

Always 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.

The two that catch people

“print(bool(" "), bool([0]), bool("False"))”

Vacuous truth

“print(any([]), all([]))”

Why == True is a trap

“print(bool(2), 2 == True, 1 == True)”

or takes any falsy value

“print(0 or "default", "" or "default")”

What usually goes wrong

Using `if not value:` when 0 is valid

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 = 1
Trusting truthiness on user input

A string of spaces is truthy because spaces are characters. Anything typed by a person needs stripping before the test.

✗ if username:
✓ if username.strip():
Using truthiness where you meant equality

`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()
Writing == True

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:
Forgetting that all([]) is True

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()
Converting text with bool()

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")
Treating None and empty as the same

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.

1.

Predict, then check: bool(0), bool(1), bool(""), bool("Python"), bool(" "), bool([0]).

Show 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 empty
2.

Given value = None, print "Value is missing" - but only for None, not for 0 or "".

Show hint

Truthiness cannot tell these apart.

Show 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))
3.

With username = "" and password = "secret", print "Login details provided" only when both have real content.

Show solution
username = "" password = "secret" if username.strip() and password.strip(): print("Login details provided") else: print("Both fields are required") # runs
4.

Given is_logged_in = True, has_subscription = False, is_public = True, allow access when logged in and either subscribed or the course is public.

Show hint

The parentheses are not optional here - and binds tighter than or.

Show 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) # True
5.

Using any(), check whether any score in [80, 40, 30] is above 70. Using all(), check whether every score is above 20.

Show solution
scores = [80, 40, 30] print(any(score > 70 for score in scores)) # True print(all(score > 20 for score in scores)) # True
6.

Show that all([]) is True, then write a check that treats an empty list of requirements as a failure.

Show 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") # runs
Coding challenge

Course 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.

It should
  • 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
Start here
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 solution
One 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 completed

Key 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

What do bool([]) and bool([False]) give?
False and True. The first list is empty; the second contains one item, and the truthiness of that item is irrelevant.
What do any([]) and all([]) return?
False and True. Nothing can be true in an empty iterable, and nothing fails either - the second is vacuous truth, and a real source of validation bugs.
Given value = 2, why does `if value:` run but `if value == True:` not?
2 is truthy, so the first passes. True is equal to 1, so 2 == True is False. It is the clearest argument for never writing == True.
Why is `if not score:` wrong when the score can legitimately be 0?
Because 0 is falsy, so a genuine score of zero is treated as missing - as are "" and []. Use `if score is None:` when you mean "not supplied".

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. 1.

    What are the falsy values in Python?

  2. 2.

    Is " " truthy or falsy?

  3. 3.

    What is the difference between None and False?

  4. 4.

    What does all([]) return, and why?

  5. 5.

    Why is `if is_active:` preferred over `if is_active == True:`?

  6. 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...