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
Every comparison operator takes two values and produces a bool: either True or False.
age: int = 25
# Equality and inequality
age == 25 # True (equal to)
age != 25 # False (not equal to)
# Ordering
age < 30 # True (less than)
age > 30 # False (greater than)
age <= 25 # True (less than or equal to)
age >= 18 # True (greater than or equal to)
Output:
True
False
True
False
True
True
All six operators work the same way: two values in, one bool out. The result is always True or False, nothing else.
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
Every Python value has a "truthiness": when passed to bool(), it becomes either True or False. The rule is simple.
These values are False (the "falsy" values):
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.
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 just learned about Python comparisons and booleans.
Here is my understanding: "Every comparison produces a bool.
You can combine bools with and, or, not."
Is my summary accurate? What important detail am I missing?
Read AI's response. Did it mention truthiness (that non-boolean values like 0 and "" also have a True/False meaning)? Did it clarify the difference between = (assignment) and == (comparison)? Compare its additions to what you learned in this lesson.
What you're learning: You are summarizing your understanding first, then using AI to find gaps. This is the same verification habit you use when reading 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]
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.
25 >= 1810 == 10.0not (5 > 3 and 2 < 1)bool("False")
Check your predictions
-
25 >= 18evaluates toTrue. 25 is greater than or equal to 18. -
10 == 10.0evaluates toTrue. Python compares values across types: the integer 10 and the float 10.0 represent the same number. This is value comparison, not identity comparison. -
not (5 > 3 and 2 < 1)evaluates toTrue. Work from the inside out:5 > 3isTrue,2 < 1isFalse.True and FalseisFalse.not FalseisTrue. -
bool("False")evaluates toTrue. This is the tricky one."False"is a non-empty string. Python does not read the word "False" and interpret it. It checks whether the string is empty or not. Since"False"has 5 characters, it is truthy, sobool("False")returnsTrue.
Run
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.
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.
Key Takeaways
-
Comparisons are calculations, not questions. Every comparison operator (
==,!=,<,>,<=,>=) takes two values and produces abool. Store the result in a typed variable:is_adult: bool = age >= 18. -
Three logical operators combine booleans.
andrequires both to beTrue.orrequires at least one.notflips the value. Use parentheses withnotto avoid precedence surprises. -
Truthiness follows one rule. Zero, empty, and
NoneareFalse. Everything else isTrue. Python does not read string content:bool("False")isTruebecause the string is not empty. -
The bool annotation matters for specification. When you write
is_short: bool = word_count < 100, you tell pyright and Claude Code that this variable holds a true-or-false value. In later chapters, these annotations become part of your function signatures.
Looking Ahead
You can now write int, float, str, and bool annotations, perform arithmetic, and write comparisons that produce booleans. One primitive type remains: None, the value that means "nothing here yet." In Lesson 4, you learn None, the union type str | None for optional values, and type conversions like int("42") and float("3.14"). You will also discover which conversions raise errors, because not every string can become a number.
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."