Comparisons and Booleans
James is adding a feature to SmartNotes: checking whether a note is "short" (under 100 words). He writes:
word_count: int = 85
is_short = word_count < 100
Emma glances at his screen. "What type is is_short?"
James pauses. "It's... true or false. A bool?"
"Then write the annotation."
is_short: bool = word_count < 100
"That is better," Emma says. "Now pyright knows, the next developer knows, and Claude Code knows. A comparison is not a question you ask Python. It is a calculation. Python evaluates word_count < 100, gets True, and stores that bool value in is_short."
James stares at the line. In Lesson 1, he stored numbers and strings in typed variables. In Lesson 2, he wrote arithmetic expressions. Now he sees that comparisons work the same way: an expression on the right, a typed result on the left.
The Six Comparison Operators
A comparison operator takes two values, compares them, and produces a bool: either True or False. Python gives you six:
== — Equal to
Checks whether two values are the same:
age: int = 25
print(age == 25)
print(age == 30)
Output:
True
False
!= — Not equal to
The opposite of ==. True when the values are different:
age: int = 25
print(age != 30)
print(age != 25)
Output:
True
False
< and > — Less than, Greater than
age: int = 25
print(age < 30) # Is 25 less than 30?
print(age > 30) # Is 25 greater than 30?
Output:
True
False
<= and >= — Less than or equal, Greater than or equal
These include the boundary value. age <= 25 is True when age is 25, because 25 equals 25:
age: int = 25
print(age <= 25) # 25 is equal to 25, so True
print(age >= 18) # 25 is greater than 18, so True
print(age <= 24) # 25 is NOT less than or equal to 24, so False
Output:
True
True
False
Quick reference
| Operator | Meaning | age = 25 Example | Result |
|---|---|---|---|
== | Equal to | age == 25 | True |
!= | Not equal to | age != 25 | False |
< | Less than | age < 30 | True |
> | Greater than | age > 30 | False |
<= | Less than or equal | age <= 25 | True |
>= | Greater than or equal | age >= 18 | True |
All six work the same way: two values in, one bool out.
A single = is assignment: age = 25 stores 25 in age. A double == is comparison: age == 25 asks whether age equals 25 and produces a bool. Mixing these up is one of the most common mistakes in Python. Pyright will catch it if you write = inside a comparison by accident.
Storing Comparison Results
James asks: "Why would I store a comparison in a variable? Why not just use word_count < 100 directly wherever I need it?"
Emma: "What if you need to check that same condition in three places?"
word_count: int = 85
# Without a variable: repeat the comparison three times
# print(word_count < 100)
# send_notification(word_count < 100)
# update_badge(word_count < 100)
# With a variable: compute once, use the result three times
is_short: bool = word_count < 100 # True
Output:
True
Storing the result in a typed variable has three benefits. First, it gives the condition a name (is_short is clearer than word_count < 100). Second, it avoids repeating the same comparison. Third, the bool annotation tells pyright and Claude Code that this variable holds a true-or-false value.
Combining Comparisons: and, or, not
Python provides three logical operators to combine bool values.
and requires both sides to be True:
age: int = 25
is_adult: bool = age >= 18 # True
is_teenager: bool = age >= 13 and age < 20 # False (25 is not < 20)
Output:
True
False
or requires at least one side to be True:
price: float = 0.0
is_free: bool = price == 0.0 # True
is_cheap: bool = price < 5.0 or price == 0.0 # True (both are True)
Output:
True
True
not flips a bool:
age: int = 25
is_minor: bool = age < 18 # False
is_not_minor: bool = not (age < 18) # True
Output:
False
True
Notice the parentheses in not (age < 18). Without them, not age < 18 would mean (not age) < 18, which is a different expression entirely. When using not with a comparison, wrap the comparison in parentheses.
Putting It All Together
Here is a complete example with typed variables, comparisons, and logical operators:
age: int = 25
is_adult: bool = age >= 18 # True
is_teenager: bool = age >= 13 and age < 20 # False
price: float = 9.99
is_free: bool = price == 0.0 # False
is_affordable: bool = price < 20.0 # True
name: str = "James"
is_empty: bool = not name # False (non-empty string is truthy)
Output:
True
False
False
True
False
That last line, not name, brings up an important concept: truthiness.
Truthiness: What Python Considers True or False
You know that True and False are bool values. But Python goes further: every value in Python can be treated as True or False, not just booleans. This is called truthiness.
Why does this matter? When you write not name (where name is a string), Python needs to decide: is name "true" or "false"? It uses the truthiness rule to decide. The rule is simple:
A value that Python treats as True is called a truthy value. A value that Python treats as False is called a falsy value. You can check any value's truthiness by passing it to bool().
These values are falsy (Python treats them as False):
bool(0) # False (zero integer)
bool(0.0) # False (zero float)
bool("") # False (empty string)
bool(None) # False (no value)
Output:
False
False
False
False
Everything else is True (the "truthy" values):
bool(1) # True (any non-zero integer)
bool(-3) # True (negative numbers are non-zero)
bool(0.001) # True (any non-zero float)
bool("hello") # True (non-empty string)
bool("0") # True (non-empty string, even though it contains "0")
Output:
True
True
True
True
True
The pattern: zero and empty are False; everything else is True.
| Value | Truthy or Falsy? | Why |
|---|---|---|
0 | Falsy | Zero integer |
0.0 | Falsy | Zero float |
"" | Falsy | Empty string (no characters) |
None | Falsy | No value at all |
False | Falsy | Already False |
1, -3, 42 | Truthy | Non-zero numbers |
0.001, 3.14 | Truthy | Non-zero floats |
"hello", "0", " " | Truthy | Non-empty strings (even "0" and " " have characters in them) |
True | Truthy | Already True |
The tricky one is "0". It looks like zero, but it is a string with one character in it. Since the string is not empty, Python treats it as truthy. Python does not read the content of strings; it only checks whether the string has any characters at all.
This is why not name in the previous section evaluates to False when name is "James". Python sees a non-empty string, treats it as truthy (True), and not True gives False.
A comparison like age >= 18 is not a question. It is a calculation. Python evaluates it and produces a value: either True or False. That value has a type: bool. You can store it in a variable, pass it to a function, or use it in another expression. Comparisons are expressions, just like 10 + 3 is an expression that produces 13.
When you write is_draft: bool = True for a SmartNotes note, you are using a bool variable. In later chapters, conditions like "is the note short AND not a draft?" will use the comparison and logical operators from this lesson. Types plus comparisons become the vocabulary for specifying note-filtering functions.
Try With AI
If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.
Prompt 1: Check Your Understanding
I'm learning Python comparisons and booleans. Here is what
I understand so far:
1. Comparison operators like == and < produce a bool (True or False)
2. I can combine bools with and, or, not
3. Every Python value has a truthiness: 0, "", and None are False,
everything else is True
What am I getting right? What am I getting wrong or missing?
Read AI's response. Did it confirm your three points? Did it add anything you missed, like the difference between = (assignment) and == (comparison)? Compare its additions to what you learned in this lesson.
What you're learning: You are stating what you know, then letting AI find gaps. This is the same verify-then-refine pattern you use when reviewing AI-generated code.
Prompt 2: Generate Boolean Expressions
Write 5 boolean expressions using typed variables that
a SmartNotes app might need. For each one, show the variable
declarations, the bool expression with a type annotation,
and a comment showing whether it evaluates to True or False.
Use only comparisons and logical operators (no if statements).
Read the output. For each expression, predict the result before reading the comment. Does your prediction match?
What you're learning: You are reading AI-generated boolean expressions and verifying them mentally, building the prediction skills you need for PRIMM.
PRIMM-AI+ Practice: Comparisons and Truthiness
Predict [AI-FREE]
Press Shift+Tab to enter Plan Mode before predicting.
For each expression below, predict whether the result is True or False. Write your prediction and a confidence score from 1 to 5 before checking the answers.
Simple comparisons:
25 >= 1810 == 10.0
Complex expressions (multiple operators joined together):
5 > 3 and 10 < 20not (5 > 3 and 2 < 1)100 > 50 or 10 > 20not (3 == 3) or 5 < 2
Truthiness traps:
bool("False")bool("") or bool("hello")
Check your predictions
-
25 >= 18→True. 25 is greater than or equal to 18. -
10 == 10.0→True. Python compares values across types: the integer 10 and the float 10.0 represent the same number. -
5 > 3 and 10 < 20→True. Both sides must beTrueforand.5 > 3isTrue,10 < 20isTrue.True and TrueisTrue. -
not (5 > 3 and 2 < 1)→True. Work from the inside out:5 > 3isTrue,2 < 1isFalse.True and FalseisFalse.not FalseisTrue. -
100 > 50 or 10 > 20→True. Only one side needs to beTrueforor.100 > 50isTrue, so the whole expression isTrueregardless of the second part. -
not (3 == 3) or 5 < 2→False.3 == 3isTrue.not TrueisFalse.5 < 2isFalse.False or FalseisFalse. Both sides areFalse, soorreturnsFalse. -
bool("False")→True. This is the classic trap."False"is a non-empty string with 5 characters. Python does not read the word; it only checks whether the string is empty. Non-empty means truthy. -
bool("") or bool("hello")→True.bool("")isFalse(empty string).bool("hello")isTrue(non-empty).False or TrueisTrue.
Run
Press Shift+Tab to exit Plan Mode.
Open Claude Code and run each expression:
Run these Python expressions and show me the results:
25 >= 18
10 == 10.0
not (5 > 3 and 2 < 1)
bool("False")
Compare the output to your predictions. If any prediction was wrong, note which one and why.
Investigate
For the tricky case bool("False"), write a one-sentence explanation of why a non-empty string is always truthy. The key insight: Python's bool() does not read the content of a string. It checks whether the string is empty. Any string with at least one character, including "False", "0", and " " (a single space), is truthy.
If any prediction surprised you, run /investigate in Claude Code and ask:
Why does bool("False") return True? What rule does Python use for truthiness of strings?
Error Taxonomy: If your prediction was wrong, classify the error. Was it a value confusion (you thought Python would read the word "False"), a precedence error (you evaluated operators in the wrong order), or a truthiness error (you did not know the rule for what counts as falsy)?
Modify
Take each of the four expressions and change one value to flip the result from True to False or vice versa. For example:
- Change
25 >= 18to something that producesFalse - Change
bool("False")to something that producesFalse
Predict the new results before running them.
Make [Mastery Gate]
Write 3 boolean expressions for real SmartNotes conditions. Each must use typed variables and a bool annotation. Do not look back at the examples.
- "Is the reading time under 5 minutes AND is the note not a draft?"
reading_time: float = 3.5
is_draft: bool = False
# Write the boolean expression:
condition_1: bool = ___
- "Is the word count zero OR is the title empty?"
word_count: int = 0
title: str = ""
# Write the boolean expression:
condition_2: bool = ___
- "Is the note long (over 1000 words) AND affordable (price under 10.0)?"
word_count: int = 1200
price: float = 7.99
# Write the boolean expression:
condition_3: bool = ___
Check your answers
-
condition_1: bool = reading_time < 5.0 and not is_draftevaluates toTrue(3.5 < 5.0 isTrue,not FalseisTrue,True and TrueisTrue). -
condition_2: bool = word_count == 0 or not titleevaluates toTrue(word_count == 0 isTrue,not ""isTrue,True or TrueisTrue). Note:title == ""also works instead ofnot title. -
condition_3: bool = word_count > 1000 and price < 10.0evaluates toTrue(1200 > 1000 isTrue, 7.99 < 10.0 isTrue,True and TrueisTrue).
If you wrote all three correctly without looking back, you can write boolean expressions for real conditions. That is the skill this lesson teaches.
James looks at his three SmartNotes conditions. Typed variables, comparisons, logical operators: each expression says exactly what it checks, and pyright knows the result is a bool.
"So every comparison is just an expression," he says. "Like arithmetic, but the answer is always True or False."
Emma nods. "And now you can specify conditions, not just values. But there is one primitive type we have not covered yet: None."
"What is None?"
"The value that means 'nothing here yet.' A note that has no category, a search that returns no result. In Lesson 4 you will learn None, how to annotate optional values with str | None, and how to convert between types with int('42') and float('3.14')."