Python Booleans
A boolean in Python represents one of exactly two values: True or False. Booleans are the backbone of every decision your program makes — every if statement, while loop, and logical comparison ultimately boils down to one of these two values. This lesson covers what booleans really are under the hood, how Python decides whether any value is “truthy” or “falsy”, and the boolean operators and, or, and not that let you combine conditions.
Overview: How Booleans Work in Python
Python has a built-in type called bool, and it has exactly two instances: True and False. Both are keywords (capitalized, unlike some other languages that use lowercase true/false), and both are singleton objects — there is only ever one True object and one False object in a running Python program, no matter how many times you write the literal.
Internally, bool is a subclass of int. That is not a coincidence or a quirk — it is a deliberate design choice inherited from how booleans were bolted onto Python’s type system. True behaves exactly like the integer 1, and False behaves exactly like 0. This means True == 1 evaluates to True, and you can even use booleans in arithmetic (True + True equals 2). You will rarely want to rely on this in real code, but understanding it explains a lot of otherwise-surprising behavior, like why sum() works on a list of booleans to count how many are True.
Any expression that produces a boolean is called a predicate. Comparison operators (==, !=, <, >, <=, >=), membership tests (in, not in), and identity tests (is, is not) all evaluate to a bool. Beyond that, Python has a broader concept: every object can be evaluated in a boolean context (like an if condition), and Python decides whether it counts as “truthy” or “falsy” by calling the object’s __bool__ method (or, if that is not defined, its __len__ method — empty collections are falsy, non-empty ones are truthy). This is why you can write if my_list: instead of if len(my_list) > 0: — Python is implicitly converting my_list to a boolean for you.
Syntax
Boolean literals and the comparison/logical operators that produce or combine them:
True
False
value1 == value2 # equality
value1 != value2 # inequality
value1 < value2 # less than
value1 > value2 # greater than
value1 <= value2 # less than or equal
value1 >= value2 # greater than or equal
bool(value) # explicit conversion to True/False
condition1 and condition2 # True only if both are truthy
condition1 or condition2 # True if either is truthy
not condition # inverts a boolean value
| Operator | Meaning | Example | Result |
|---|---|---|---|
== |
equal to | 3 == 3.0 |
True |
!= |
not equal to | 3 != 4 |
True |
<, > |
strictly less/greater than | 5 > 2 |
True |
<=, >= |
less/greater than or equal | 5 >= 5 |
True |
and |
logical AND | True and False |
False |
or |
logical OR | True or False |
True |
not |
logical NOT | not True |
False |
Examples
Example 1: Basic boolean values and comparisons
is_active = True
is_admin = False
print(is_active)
print(is_admin)
print(type(is_active))
age = 20
can_vote = age >= 18
print(can_vote)
print(10 == 10)
print(10 != 5)
print(5 > 10)
True
False
<class 'bool'>
True
True
True
False
Each comparison operator returns a fresh bool value. Notice that type(is_active) prints <class 'bool'> — confirming that True and False are real objects of the bool type, not just special syntax.
Example 2: Truthy and falsy values
values = [0, 1, -1, 0.0, "", "hello", [], [1, 2], {}, None, (), " "]
for value in values:
print(value, "->", bool(value))
0 -> False
1 -> True
-1 -> True
0.0 -> False
-> False
hello -> True
[] -> False
[1, 2] -> True
{} -> False
None -> False
() -> False
-> True
Every value in Python is truthy unless Python defines it as falsy. The falsy values include 0, 0.0, empty strings, empty lists, empty dicts, empty tuples, and None. A non-empty string containing only whitespace (like " ") is truthy, because it has a length greater than zero — the content doesn’t matter, only whether the collection is empty.
Example 3: Short-circuit evaluation
def check_stock(item: str) -> bool:
print(f"Checking stock for {item}...")
return item == "widget"
result = False and check_stock("widget")
print("Result 1:", result)
result = True or check_stock("gadget")
print("Result 2:", result)
print(0 or "default")
print("value" and 42)
print(not [])
print(not "text")
Result 1: False
Result 2: True
default
42
True
False
Python’s and/or operators are short-circuiting: they stop evaluating as soon as the result is determined, and check_stock() never actually runs in either case here (no “Checking stock…” line is printed). Also notice that and/or don’t always return True/False — they return one of their actual operands. 0 or "default" returns "default" because 0 is falsy, so Python moves on to (and returns) the second operand. "value" and 42 returns 42 because "value" is truthy, so and evaluates and returns the second operand.
Under the Hood: How Truthiness Is Determined
When you write if some_object:, Python doesn’t require some_object to already be a bool. Instead, it effectively calls bool(some_object) behind the scenes. The bool() constructor follows this lookup order:
- If the object defines a
__bool__method, Python calls it and uses its return value. - Otherwise, if the object defines
__len__, Python calls it — a length of0means falsy, anything else means truthy. This is why empty lists, strings, dicts, and sets are all falsy. - If neither is defined, the object is truthy by default (this is why custom class instances are truthy unless you explicitly say otherwise).
Because bool is a subclass of int, you can freely mix booleans with numeric operations — though it’s rarely a good idea to lean on this in application logic:
print(True == 1)
print(False == 0)
print(True + True + False + True)
print(isinstance(True, int))
print(sum([True, False, True, True]))
True
False
3
True
3
This is genuinely useful in one common pattern: counting how many items in an iterable satisfy a condition, by summing a list (or generator) of booleans, as shown by sum([True, False, True, True]) returning 3 — each True contributes 1 to the total.
Common Mistakes
Mistake 1: Comparing explicitly to True or False
Writing if is_valid == True: works, but it’s redundant and considered poor style — if is_valid is already a boolean, just write if is_valid:. Worse, == True can silently misbehave with non-boolean truthy values: 1 == True is True, but 2 == True is False even though 2 is truthy. Prefer if is_valid: and if not is_valid: over explicit comparisons.
Mistake 2: Using = instead of == in a condition
A single = is assignment; a double == is comparison. Mixing them up inside a condition is a SyntaxError in Python (unlike some languages where it silently becomes a bug), so at least Python catches it for you before the program runs:
x = 5
if x = 5:
print("five")
Running this raises SyntaxError: invalid syntax (Python even suggests you might have meant ==). The fix is simply to use if x == 5:.
Mistake 3: Using `or` for defaults when falsy values are valid
A very common bug is using value or default to supply a fallback, forgetting that legitimate falsy values like 0, "", or an empty list will also trigger the fallback:
def get_discount(percent: int | None) -> int:
# Bug: 0 is falsy, so a valid 0% discount gets replaced by the default
return percent or 10
print(get_discount(0))
print(get_discount(25))
def get_discount_fixed(percent: int | None) -> int:
return percent if percent is not None else 10
print(get_discount_fixed(0))
print(get_discount_fixed(25))
10
25
0
25
The buggy version returns 10 for a genuinely valid 0% discount, because 0 or 10 evaluates to 10. The fixed version explicitly checks is not None, which correctly distinguishes “no value was given” from “the value happens to be falsy”.
Best Practices
- Write
if flag:andif not flag:instead ofif flag == True:orif flag == False:. - Use
is/is notwhen comparing againstNone(e.g.if value is None:), never==, sinceischecks identity against the singleNonesingleton and can’t be fooled by a custom__eq__. - Prefer
if percent is not None:overif percent:whenever0,"", or an empty collection would be a valid, meaningful value. - Name boolean variables and functions so their truthiness reads naturally:
is_active,has_permission,can_editrather than vague names likeflagorstatus. - Rely on short-circuit evaluation intentionally, e.g.
user and user.is_adminsafely avoids anAttributeErrorwhenuserisNone, but keep such chains short and readable. - Avoid relying on
boolbeing a subclass ofintin application logic (like usingTrueas a dictionary key alongside1) — it works, but it obscures intent.
Practice Exercises
Exercise 1
Write a function is_even(number) that returns True if a number is even and False otherwise, using the modulo operator. Test it with 4 and 7.
Exercise 2
Given the list data = [3, 0, "", "hi", None, [1], [], False, True], write a loop that prints only the truthy elements from the list.
Exercise 3
Write a function can_checkout(cart_items, is_logged_in) that returns True only if cart_items is a non-empty list and is_logged_in is True. Use short-circuit evaluation with and rather than nested if statements. What should it return for can_checkout([], True) and can_checkout(["book"], False)?
Summary
boolis a built-in type with exactly two values,TrueandFalse, and it is a subclass ofint(True == 1,False == 0).- Comparison operators (
==,!=,<,>,<=,>=) and identity/membership tests (is,in) all produce booleans. - Every object has a truthiness, determined via
__bool__or, failing that,__len__(empty collections and0-like values are falsy). andandorshort-circuit and return one of their actual operands, not necessarilyTrue/False.- Avoid
== True/== Falsecomparisons, and useis Nonerather than falsy checks when0or empty values are legitimate. - Use
bool()to explicitly convert any value into its truthy/falsy equivalent.
