Skip to main content

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.

Common beginner confusion

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.

Beginner track

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.

SmartNotes Connection

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

Opening Claude Code

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.

  1. 25 >= 18
  2. 10 == 10.0
  3. not (5 > 3 and 2 < 1)
  4. bool("False")
Check your predictions
  1. 25 >= 18 evaluates to True. 25 is greater than or equal to 18.

  2. 10 == 10.0 evaluates to True. Python compares values across types: the integer 10 and the float 10.0 represent the same number. This is value comparison, not identity comparison.

  3. not (5 > 3 and 2 < 1) evaluates to True. Work from the inside out: 5 > 3 is True, 2 < 1 is False. True and False is False. not False is True.

  4. bool("False") evaluates to True. 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, so bool("False") returns True.

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 >= 18 to something that produces False
  • Change bool("False") to something that produces False

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.

  1. "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 = ___
  1. "Is the word count zero OR is the title empty?"
word_count: int = 0
title: str = ""
# Write the boolean expression:
condition_2: bool = ___
  1. "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
  1. condition_1: bool = reading_time < 5.0 and not is_draft evaluates to True (3.5 < 5.0 is True, not False is True, True and True is True).

  2. condition_2: bool = word_count == 0 or not title evaluates to True (word_count == 0 is True, not "" is True, True or True is True). Note: title == "" also works instead of not title.

  3. condition_3: bool = word_count > 1000 and price < 10.0 evaluates to True (1200 > 1000 is True, 7.99 < 10.0 is True, True and True is True).

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

  1. Comparisons are calculations, not questions. Every comparison operator (==, !=, <, >, <=, >=) takes two values and produces a bool. Store the result in a typed variable: is_adult: bool = age >= 18.

  2. Three logical operators combine booleans. and requires both to be True. or requires at least one. not flips the value. Use parentheses with not to avoid precedence surprises.

  3. Truthiness follows one rule. Zero, empty, and None are False. Everything else is True. Python does not read string content: bool("False") is True because the string is not empty.

  4. 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."