Booleans, None, and truthiness
Two small types with outsized importance. Booleans are how every decision in
every program gets made, and None is Python's way of saying "nothing here" —
which is a different thing from zero or an empty string, and confusing them
causes real bugs.
Booleans
There are exactly two boolean values, and the capital letters are required:
is_active = True
is_deleted = False
true and false in lowercase are a NameError. This trips up anyone arriving
from JavaScript.
Booleans are usually produced by a comparison rather than typed by hand:
age = 25
print(age > 18) # True
print(age == 25) # True
print(age != 25) # False
Note == against =. One asks a question, the other gives an instruction.
Writing if age = 25: is a SyntaxError, which is Python protecting you — in C
it would silently assign and cause a genuinely nasty bug.
Combining conditions
age = 25
has_licence = True
print(age >= 18 and has_licence) # True
print(age < 18 or has_licence) # True
print(not has_licence) # False
and needs both sides true. or needs at least one. not flips it.
Python lets you chain comparisons the way mathematics does, which most languages do not:
score = 75
print(0 <= score <= 100) # True
That reads as "score is between 0 and 100". In most languages you would write
score >= 0 && score <= 100.
Short-circuiting
and and or stop as soon as the answer is known:
def expensive_check():
print("this ran")
return True
result = False and expensive_check()
Nothing is printed. Since the left side of and is already False, the answer
is False regardless, so Python never evaluates the right side.
This is not a curiosity — it is the standard way to guard an operation:
if name and name.strip():
print("valid name")
If name is empty, the left side is falsy and name.strip() never runs. Order
the conditions so the cheap or protective one comes first.
Truthiness
Here is the part that surprises people. Python will accept any value where a boolean is expected, and it has rules for which ones count as false.
Falsy values — everything in this list behaves as False:
False
None
0
0.0
"" # empty string
[] # empty list
{} # empty dictionary
() # empty tuple
Everything else is truthy. Including "0", "False", -1, and [0] —
all of which are non-empty, and therefore true.
Check for yourself with bool():
print(bool(0)) # False
print(bool("")) # False
print(bool("0")) # True — a non-empty string
print(bool([])) # False
print(bool([0])) # True — a list with one item in it
This is why you can write:
items = []
if not items:
print("nothing to show")
rather than if len(items) == 0:. The short form is idiomatic Python and you
should write it — but only once you genuinely understand what makes a value
falsy, because the same convenience causes the bug in the next section.
None
None means "no value". Not zero, not empty — absent.
middle_name = None
It appears constantly in real code: a database field nobody filled in, a search that found nothing, a function that returns nothing.
That last one catches everybody:
numbers = [3, 1, 2]
result = numbers.sort()
print(result)
None
sort() sorts the list in place and returns nothing. The list was sorted —
but result is None, and the next line that uses it fails confusingly. A
method that changes something in place usually returns None; a method that
returns a new value usually does not change the original.
Always test None with is
value = None
print(value is None) # correct
print(value == None) # works, but do not
Use is. It asks "are these the same object?", which is exactly the question,
and it cannot be broken by a class that defines its own ==. Linters will flag
== None.
The bug this all leads to
count = 0
if count:
print("we have a count")
else:
print("no count")
no count
0 is falsy, so this reports "no count" even though the count is genuinely
zero — a real, known value. If you meant "was a count provided at all?", 0 is
a perfectly good answer and this code throws it away.
Say what you actually mean:
if count is not None:
print(f"we have a count: {count}")
Same trap with strings:
name = ""
print(name is None) # False — it is a string, just an empty one
print(bool(name)) # False — but it is falsy
An empty string and a missing string are different situations. A form submitted
with the name box left blank gives "". A form that never had a name field
gives None. Treating them the same hides the difference between "the user left
it empty" and "we never asked".
The rule: when 0, "" or an empty list are legitimate values in your
program, test is None explicitly. Use truthiness when you genuinely mean
"empty or missing, either way".
Booleans are numbers
A small oddity that is occasionally useful:
print(True + True) # 2
print(sum([True, False, True])) # 2
True is 1 and False is 0. Summing a list of booleans counts how many are
true, which is a neat way to count matches. Do not lean on it for anything else.
Practice
- Predict the output of each, then check:
bool(""),bool(" "),bool(0),bool("0"),bool([]),bool([[]]). The last two are the interesting ones. - Set
age = 20andhas_ticket = False. Write one condition that isTrueonly when someone is 18 or over and has a ticket. Then one that isTruewhen they are under 18 or have no ticket. - Run
numbers = [3, 1, 2]thenprint(numbers.sort()). Explain why it printsNone, then print the sorted list correctly. - Write code where
if value:andif value is not None:give different answers. Usevalue = 0. - Set
middle_name = Noneand print a full name that omits the middle name when it is absent. You will need anif— next module covers them properly, but attempt it now.
Next: converting between types, and why input() will trip you up exactly once.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship