This lesson introduces floor division and the modulus operator with the uses that make them worth knowing, then the relational and logical operators and the bool type, assembling everything an if statement needs for a condition.
Subject: Python · 65 slides · code lesson
Open the interactive version of this deck
Title
Python · Chapter 5 — Conditionals and recursion
§5.1-5.3, pp. 39-40
Objectives
Five things, each one you can check yourself at an interpreter prompt.
Think Python, 2nd edition — Allen B. Downey §5.1-5.3, pp. 39-40 — the pages these objectives are drawn from
Warm-up
You know how to divide. The question is what you want the answer to look like.
Discussion prompt
A film runs for 105 minutes. Work out how many whole hours that is, and how many minutes are left over. Then write down what Python's ordinary division gives you for 105 divided by 60, and say why that is not quite the answer you wanted.
Hint: One hour and forty-five minutes. Python gives you something else.
Answer:
Python's division gives 1.75. That is a correct number and the wrong shape: nobody says a film is 1.75 hours long.
You wanted two numbers — one whole hour, and forty-five minutes left over — and ordinary division gives you one number with the two mixed together.
Python has an operator for each half of that answer, and this lesson starts with both. They are the operators that make working in whole numbers practical.
Concept
A boolean expression is an expression that is either true or false. That sounds like a description of a question, and it is really a description of a VALUE — True and False are values with a type of their own, and a condition is just an expression that produces one.
boolean expression — An expression whose value is either True or False.
This matters because everything you already know about expressions applies. A boolean expression can be assigned to a variable, passed to a function, or combined with operators — and in the next lesson it will be the thing an if statement looks at.
Figure (svg): A diagram showing two values entering a relational operator and a True or False value coming out
Think Python, 2nd edition — Allen B. Downey §5.1-5.3, pp. 40-40
Section
Section 1
Concept
The floor division operator divides two numbers and rounds down to an integer. It is written as two slashes, and it exists because ordinary division does not give the answer you usually want when you are counting whole things.
>>> minutes = 105
>>> minutes / 60
1.75
>>> hours = minutes // 60
>>> hours
1| Expression | Which operator | Result |
|---|---|---|
| minutes / 60 | ordinary division | 1.75, a float |
| minutes // 60 | floor division | 1, an int |
| the difference | the fraction is dropped, not shown | a whole number of hours |
We do not normally write hours with decimal points. Floor division returns the integer number of hours, rounding down — which is what you want when the thing being counted cannot be fractional.
Think Python, 2nd edition — Allen B. Downey §5.1-5.3, pp. 39-39
Picture it
Both are correct. They answer different questions.
Figure (svg): Two columns contrasting ordinary division producing a float with floor division producing an int
This also answers a question left open in lesson 1b: the double-slash operator that produced an int from 84 and 2 was floor division, and now you know why it exists.
Worked example
Two operators, two halves of one answer.
>>> minutes = 105
>>> hours = minutes // 60
>>> remainder = minutes - hours * 60
>>> hours, remainder
(1, 45)| Step | What it computes | Value |
|---|---|---|
| minutes // 60 | how many whole hours fit | 1 |
| hours * 60 | how many minutes those hours account for | 60 |
| minutes - 60 | what is left over | 45 |
Get the whole hours with floor division.
Why: One whole hour fits into 105 minutes. The rounding-down is what makes this the count of COMPLETE hours rather than a fraction.
Work out what those hours used up.
Why: One hour is sixty minutes, so sixty of the 105 are accounted for.
Subtract to find the leftover.
Why: Forty-five minutes remain, which is the answer you wanted from the warm-up.
Figure (svg): The state of the program after each line of Worked example hours and minutes, drawn as a ladder with one rung per traced line
One hour and forty-five minutes. Floor division gave the whole part; a subtraction recovered the remainder.
Verify: Check that the two parts reconstruct the original.
Why: One hour is sixty minutes, plus forty-five, is 105 — the number you started with. Any split of a quantity into a whole part and a remainder must reassemble, and checking that catches an off-by-one immediately.
Prediction
Divide, then round down.
Predict first
What is the value of 17 // 5?
Correct: 3 — seventeen divided by five is 3.4, and floor division rounds down to 3.
Why: Three whole fives fit into seventeen, with two left over. Note that the result is an int rather than a float: floor division on two ints gives an int, which is the whole reason it exists as a separate operator from the single-slash division that always gives a float.
Worked example
For positive numbers these are the same. Find the case where they differ.
>>> 7 // 2
3
>>> -7 // 2
-4
>>> int(-7 / 2)
-3| Expression | What happens | Result |
|---|---|---|
| 7 // 2 | 3.5 rounded down | 3 |
| -7 // 2 | -3.5 rounded DOWN | -4 |
| int(-7 / 2) | -3.5 chopped toward zero | -3 |
Check the positive case.
Why: Seven divided by two is 3.5, and rounding down gives 3. Chopping toward zero would also give 3, so this case tells you nothing about which rule is operating.
Check the negative case.
Why: Minus seven over two is -3.5. Rounding DOWN means going to the more negative side, giving -4.
Compare with int().
Why: int chops toward zero, from lesson 3a, giving -3. So the two are genuinely different operations, and they differ exactly on negative numbers.
Figure (svg): A number line showing minus three point five with arrows to minus four for floor division and minus three for int
Floor division rounds down — toward more negative — while int() chops toward zero. They agree on positive numbers and disagree on negatives.
Verify: Check that the remainder still reconstructs the original in the negative case.
Why: -4 times 2 is -8, and -8 plus 1 is -7, so the remainder is 1 rather than -1. The two operators are designed to agree with each other: floor division and modulus always satisfy the reconstruction, which is why they round the way they do.
Trap
A student tests both on positive numbers, sees identical answers, and treats them as interchangeable.
Generalise from the cases that happen to agree
Why: On positive numbers they always agree, and most test data is positive.
The first negative value produces answers that differ by one, in a program that has been correct for months.
They round differently, and the difference only shows on negatives.
Floor division rounds DOWN, always
Why: Toward the more negative side of the number line, whatever the sign.
int() chops toward zero
Why: Which is upward for a negative number.
Choose deliberately. Counting complete units usually wants floor division, because it agrees with the modulus operator and the two reconstruct the original — which is a property worth having.
Discrimination
The question decides the operator.
Sort into buckets
For each question, is the answer given by / or by //?
Faded example
One floor division and one subtraction, exactly as with the film.
Fill in the blanks
total = 500
minutes = total // 60
seconds = total - minutes * 60
Why: Floor division gives the number of complete minutes, which is 8, and the subtraction recovers the 20 seconds left over. Using a single slash here would give 8.333..., and multiplying that back by 60 would give 500 exactly, leaving a remainder of zero — which is arithmetically true and useless, because you wanted the split rather than the original number back.
Socratic
Most languages have one. Python has two on purpose.
Discussion prompt
Python 2 used a single slash for both, choosing floor division when both operands were integers and ordinary division otherwise. Name one advantage of that design and one problem with it.
Hint: Think about a division where you do not know the types in advance.
Answer:
The advantage: one operator to learn, and integer arithmetic behaves like integer arithmetic without extra syntax.
The problem is the one lesson 1b's socratic probe predicted: the MEANING of the operator would depend on the types of its operands, so you could not tell by reading a line what it would do. A division of two variables might be floor division on Tuesday and ordinary division on Wednesday, depending on the data.
Python 3 split them so that each operator does one thing always. The book flags the difference explicitly, because it is the change most likely to bite somebody reading older code.
Section
Section 2
Concept
The modulus operator divides two numbers and returns the remainder. It is written with a percent sign, and it has nothing to do with percentages.
>>> minutes = 105
>>> remainder = minutes % 60
>>> remainder
45| Expression | What it means | Value |
|---|---|---|
| 105 % 60 | how much is left after taking out whole 60s | 45 |
| compare | minutes - (minutes // 60) * 60 | also 45 |
| the pair | // gives the whole part, % gives the rest | together they split a number |
The modulus operator is more useful than it seems. You can check whether one number is divisible by another — if x % y is zero, then x is divisible by y. And you can extract the right-most digits: x % 10 gives the last digit of x, and x % 100 the last two.
Think Python, 2nd edition — Allen B. Downey §5.1-5.3, pp. 39-40
Picture it
The pair always reconstructs the original, which is what makes them a pair.
Figure (svg): A diagram showing 105 splitting into one whole hour via floor division and forty-five minutes via modulus
That reconstruction is a check you can run on any pair of results, and it is also the reason floor division rounds down rather than toward zero.
Worked example
The first of the two uses the book highlights. It becomes a condition in the next lesson.
>>> 12 % 3
0
>>> 13 % 3
1
>>> 100 % 10
0| Expression | What the remainder means | Conclusion |
|---|---|---|
| 12 % 3 | nothing left over | 0, so 12 is divisible by 3 |
| 13 % 3 | one left over | 1, so 13 is not |
| 100 % 10 | nothing left over | 0, so 100 is divisible by 10 |
State the rule.
Why: If x % y is zero, then x is divisible by y. A zero remainder means the division came out exactly.
Check a case that is not divisible.
Why: 13 % 3 is 1, so three does not divide thirteen. The remainder tells you not only THAT it fails but by how much.
Notice where this is heading.
Why: x % y == 0 is a boolean expression, and in the next lesson it will be the condition of an if statement — which is why divisibility is worth a section this early.
Figure (svg): The state of the program after each line of Worked example testing divisibility, drawn as a ladder with one rung per traced line
A remainder of zero means divisible. The test is written as x % y equal to zero, and it works for any pair of whole numbers.
Verify: Check the rule against a number you know is even.
Why: Any even number gives 0 for n % 2, and any odd number gives 1. Testing on a case where you already know the answer is how you confirm you have the operator the right way round — n % 2 rather than 2 % n, which gives something quite different.
Prediction
The dividend is smaller than the divisor. Reason it through.
Predict first
What is the value of 7 % 10?
Correct: 7 — no whole tens fit into seven, so all of it is left over.
Why: Floor division gives 7 // 10 = 0, meaning zero whole tens fit. The reconstruction rule then forces the remainder to be 7, since 0 times 10 plus 7 is 7. This is also consistent with the digit rule: the last digit of 7 is 7. People often expect 0 here by analogy with dividing a small number by a big one, which is what ordinary division would suggest.
Worked example
The second use, and it is less obvious. Work out why it holds.
>>> 1234 % 10
4
>>> 1234 % 100
34
>>> 1234 // 10 % 10
3| Expression | What it extracts | Result |
|---|---|---|
| 1234 % 10 | the last digit | 4 |
| 1234 % 100 | the last two digits | 34 |
| 1234 // 10 % 10 | drop the last digit, then take the new last | 3 |
See why % 10 gives the last digit.
Why: Dividing by ten takes out all the tens, hundreds and thousands exactly; whatever is left over is what was in the units column.
Extend it.
Why: Similarly x % 100 yields the last two digits, because dividing by a hundred takes out everything above the tens column.
Combine with floor division for the digit before last.
Why: 1234 // 10 is 123, discarding the last digit; taking that modulo 10 gives 3, which was the tens digit of the original.
Figure (svg): The digits of 1234 drawn as boxes with the last one highlighted as what modulo ten extracts
The remainder on division by ten is the last digit, by a hundred the last two. Combining floor division with modulus reaches any digit you like.
Verify: Reconstruct the number from its parts.
Why: 1234 // 10 is 123 and 1234 % 10 is 4, and 123 times 10 plus 4 is 1234. The same reconstruction check as before, now peeling one digit rather than one hour — which is a good sign that the two operators really are one idea applied at different scales.
Trap
A student sees 105 % 60 and expects something to do with proportions — perhaps 105 percent of 60.
Import the symbol's everyday meaning
Why: The percent sign means exactly one thing outside programming, and it is not this.
The expectation produces confident wrong predictions: 63 rather than 45, with no error to correct it.
In Python the percent sign is the modulus operator and has nothing to do with percentages.
Read x % y as the remainder when x is divided by y
Why: Say it that way out loud until the symbol stops suggesting proportions.
Compute a percentage with arithmetic instead
Why: x * 100 / y, which uses no special operator at all.
This is the same class of mistake as the caret in lesson 1b: a symbol whose everyday meaning differs from its Python meaning, producing a confident wrong answer rather than an error.
Matching
Floor division and modulus, used together, reach any part of a number.
Match the pairs
Why: Modulus keeps the right-hand end of a number and floor division keeps the left, which is why they combine so well. The third one is the same idea with a divisor of two: the remainder on division by two is 0 or 1, and those are exactly the two cases of even and odd — which is why it becomes the standard evenness test in the next lesson.
Fill the middle
The first part of the leap-year rule is a divisibility test.
Fill in the blanks
year = 2024
is_divisible_by_4 = year % 4 == 0
Why: The remainder on division by four is zero exactly when the year is divisible by four, so this expression is True for 2024 and False for 2023. Note that the whole line is a boolean expression assigned to a name — which is the point the big idea was making: a condition is a value, and it can be stored like any other. The full leap-year rule needs two more divisibility tests, and the next lesson has the syntax to combine them.
Edge cases
A divisor of one is a legitimate edge case. Predict it, then explain it.
Discussion prompt
What is n % 1 for any whole number n? Explain your answer using the reconstruction rule, and then say what n % 1 gives when n is a float such as 3.75.
Hint: How many whole ones fit into n?
Answer:
For a whole number it is always 0. Exactly n whole ones fit into n, so nothing is left over, and the reconstruction n // 1 times 1 plus 0 gives n.
For a float it is the fractional part: 3.75 % 1 is 0.75. Three whole ones fit, leaving three quarters — the same rule, applied to a number that has a fraction to leave over.
That makes n % 1 a compact way of asking what is the fractional part of this number, which is a genuinely useful thing to know and follows from the definition rather than being a special case.
Section
Section 3
Concept
A boolean expression is an expression that is either true or false. True and False are special values that belong to the type bool, and they are not strings.
>>> 5 == 5
True
>>> 5 == 6
False
>>> type(True)
<class 'bool'>| Expression | Why | Value |
|---|---|---|
| 5 == 5 | the two operands are equal | True |
| 5 == 6 | they are not | False |
| type(True) | a type of its own | bool |
Although these operations are probably familiar, the Python symbols differ from the mathematical ones. There is no such thing as the reversed forms — the equals sign always comes second in the two-character operators.
x == y — x is equal to yx != y — x is not equal to yx > y — x is greater than yx < y — x is less than yx >= y — x is greater than or equal to yx <= y — x is less than or equal to yThink Python, 2nd edition — Allen B. Downey §5.1-5.3, pp. 40-40
Picture it
int, float, str — and now bool, with exactly two values in it.
Figure (svg): Two columns comparing the bool type with the three types met earlier, showing how many values each has
True and False are written with capital letters and no quotation marks. 'True' with quotes is a string, and by lesson 1b's rule it is a completely different value.
Worked example
Predict each result before advancing. All six produce a bool.
>>> 5 == 5
True
>>> 5 != 5
False
>>> 5 > 3
True
>>> 5 <= 5
True| Expression | What it asks | Value |
|---|---|---|
| 5 == 5 | equal | True |
| 5 != 5 | not equal — but they are | False |
| 5 > 3 | greater than | True |
| 5 <= 5 | less than OR equal | True |
Read each as a question with a yes-or-no answer.
Why: Is five equal to five? Yes. Is five not equal to five? No.
Watch the two-character operators carefully.
Why: The equals sign comes second in all of them: !=, >=, <=. There is no such thing as =< or =>, and writing one is a syntax error.
Note the fourth case.
Why: Five is not less than five, but it is equal to it, and <= asks whether EITHER holds. That is why it is True.
Figure (svg): The state of the program after each line of Worked example the six relational operators, drawn as a ladder with one rung per traced line
True, False, True, True. Every relational operator produces a bool, whatever types it was comparing.
Verify: Check the complementary pairs.
Why: 5 == 5 and 5 != 5 give opposite answers, as do 5 > 3 and 5 <= 3. Each operator has an exact opposite, and checking that a pair disagrees is a quick way to confirm you have read the symbols correctly.
Prediction
Two values that look the same and are not.
Predict first
What is the value of '5' == 5?
Correct: False — a str and an int are never equal, however similar they look.
Why: Notice that this is not an error. Python is perfectly willing to compare values of different types with the equality operator; it simply answers no. That silence is what makes it dangerous — unlike '5' + 5, which raises a TypeError and stops you, this quietly gives False and lets the program continue with a condition that will never be true.
Worked example
The most common syntax error in early Python. Cause it deliberately.
if x = 5:
print('five')| What was written | What it is | Result |
|---|---|---|
| x = 5 | an assignment, which is a statement | statements have no value |
| if needs a value | it got a statement | SyntaxError |
| x == 5 | a comparison, which is an expression | what was meant |
Recall the distinction from lesson 2a.
Why: A single equals sign is an assignment operator and produces no value. A double equals sign is a relational operator and produces a bool.
See why the if statement rejects it.
Why: An if statement needs a condition — an expression with a value. An assignment is a statement, and statements do not have values, so there is nothing for the if to test.
Write what was meant.
Why: if x == 5: compares, produces True or False, and the if statement has something to work with.
Figure (svg): Two columns contrasting the assignment operator with the equality operator, showing what each produces
A SyntaxError. A common error is to use a single equal sign instead of a double equal sign — one assigns and the other compares, and only the second produces a value an if statement can use.
Verify: Check the diagnosis against the expression-versus-statement test from lesson 2a.
Why: The test was: could this be used where a value is expected? An assignment could not, which predicted this error before you ever saw an if statement. A rule from three lessons ago explaining a new error is a good sign it was the right rule.
Trap
A program reads '42' from the user and tests whether it equals 42.
Compare for the meaning rather than the value
Why: Both are obviously forty-two to a human reader.
The comparison gives False, with no error at all. A str and an int are never equal, whatever their contents look like, so the branch never runs and the program silently does the wrong thing.
Compare values of the same type, converting first if necessary.
Convert the text to a number at the boundary
Why: int(answer) == 42, using the conversion from lesson 3a.
Or compare as text if that is what you mean
Why: answer == '42' is also correct, and it is the right choice if you care about the exact characters typed.
This becomes urgent in the next lesson: input always returns a string, so every comparison with a number needs a conversion — and forgetting it produces a condition that is silently always False.
Sorting
Relational operators produce bools. Assignments and arithmetic do not.
Sort into buckets
For each line, does it produce a boolean value?
Two truths and a lie
Two are true. Keep the lie.
Eliminate the wrong options
Rule out the two true statements.
Survives elimination: C
Why: C is the lie, and it is the same shape as the None-versus-'None' confusion from lesson 3c. True and False are special values that belong to the type bool; they are not strings. Quotation marks make a str, whatever is inside them, so 'True' is a four-character piece of text with a completely different type.
Notation
Six operators, and the two-character ones have a rule.
Annotate
The equals-second rule is worth memorising deliberately, because the mistake is a syntax error that stops the program rather than a wrong answer — which makes it annoying rather than dangerous.
Section
Section 4
Concept
There are three logical operators: and, or, and not. Their meaning is similar to their meaning in English, which makes them unusually easy to read — and slightly dangerous, because English is looser than Python.
>>> x = 5
>>> x > 0 and x < 10
True
>>> x % 2 == 0 or x % 3 == 0
False
>>> not (x > 10)
True| Operator | When it is True | This case |
|---|---|---|
| and | True only if BOTH sides are true | 5 is above 0 and below 10 |
| or | True if EITHER side is true, or both | 5 is divisible by neither 2 nor 3 |
| not | reverses a boolean | 5 is not above 10, so this is True |
The book's examples are worth keeping: x greater than 0 and x less than 10 is true only if x is in that range, and n modulo 2 equal to 0 or n modulo 3 equal to 0 is true if the number is divisible by 2 or by 3 or by both.
Think Python, 2nd edition — Allen B. Downey §5.1-5.3, pp. 40-40
Picture it
Two operators that combine, one that reverses.
Figure (svg): Two columns showing what and requires against what or requires, with not shown as a reversal
The English word or is often exclusive — tea or coffee usually means one, not both. Python's or is inclusive: True or True is True.
Worked example
Two comparisons, joined. Work out why both are needed.
>>> x = 5
>>> x > 0 and x < 10
True
>>> x = 50
>>> x > 0 and x < 10
False| Case | The two comparisons | Result |
|---|---|---|
| x = 5 | 5 > 0 is True; 5 < 10 is True | True and True gives True |
| x = 50 | 50 > 0 is True; 50 < 10 is False | True and False gives False |
| the rule | and needs both | one failure is enough |
Evaluate each side separately.
Why: Each is an ordinary relational expression producing a bool. The logical operator combines the two results, not the original numbers.
Apply the rule for and.
Why: It is true only if both sides are true. With 50, the first side holds and the second does not, so the whole thing is False.
Notice what a single test could not do.
Why: No single comparison expresses between 0 and 10. Two are needed, and the logical operator is how they are joined.
Figure (svg): The state of the program after each line of Worked example a range test with and, drawn as a ladder with one rung per traced line
True for 5 and False for 50. The and operator requires both conditions, so failing either one is enough to make the whole expression False.
Verify: Test a value that fails the other side.
Why: With x = -3 the first comparison fails and the second succeeds, and the result is again False. Testing both ways round confirms that and is genuinely symmetric in what it requires, rather than only checking the first condition.
Prediction
Evaluate each side, then combine.
n = 9
n % 2 == 0 or n % 3 == 0| Sub-expression | Value | Result |
|---|---|---|
| n % 2 | 9 divided by 2 leaves 1 | 1 == 0 is False |
| n % 3 | 9 divided by 3 leaves 0 | 0 == 0 is True |
| False or True | or needs only one | True |
Predict first
What is the value of this expression when n is 9?
Correct: True — nine is not divisible by two, but it is divisible by three, and or needs only one side to hold.
Why: This is the book's own example. The first comparison is False because 9 % 2 is 1; the second is True because 9 % 3 is 0. The or operator is true if either or both of the conditions is true, so one success is enough. Note how each half combines two ideas from this lesson: modulus for the divisibility test, and a relational operator to turn the remainder into a bool.
Worked example
The book raises this and then advises against relying on it. Both halves matter.
>>> 42 and True
True
>>> 0 and True
0| Expression | What Python does | Note |
|---|---|---|
| 42 and True | any nonzero number counts as true | True |
| 0 and True | zero counts as false | 0 |
| the advice | useful, subtle, avoidable | avoid unless you know why |
Notice that this works at all.
Why: Strictly speaking, the operands of the logical operators should be boolean expressions, but Python is not very strict. Any nonzero number is interpreted as True.
Notice the second result is not a bool.
Why: 0 and True gives 0, not False. The operators do not always return a bool, which is one of the subtleties the book warns about.
Take the advice.
Why: This flexibility can be useful, but there are some subtleties that might be confusing, and you might want to avoid it unless you know what you are doing.
Figure (svg): A panel listing which values count as false in Python and noting that everything else counts as true
Python accepts non-boolean operands, treating any nonzero number as true — and it may hand back one of the operands rather than True or False. It is worth recognising and not worth relying on this early.
Verify: Write the explicit version and compare.
Why: n != 0 and True is unambiguous, always produces a bool, and reads as what it means. Being able to write the explicit version is the real defence: the shortcut is only dangerous when it is the only form you know.
Trap
Meaning x is between 0 and 10, a student writes 0 < x and < 10, or x > 0 and < 10.
Carry over English's ellipsis
Why: In English the second x is understood and omitting it is natural.
Python has no such convention. The and operator joins two complete expressions, and < 10 on its own is not one, so this is a syntax error.
Each side of a logical operator must be a complete expression.
Write both comparisons in full
Why: x > 0 and x < 10. The x appears twice because there are two comparisons.
Or use Python's chained form, which does allow the ellipsis
Why: 0 < x < 10 is legal and means exactly the same thing — the next lesson introduces it.
The chained form exists precisely because the English reading is so natural. It is the one place Python bends toward how you would say it, and it is limited to comparisons.
Comparison
Fill the blanks from the definitions.
Comparison matrix
| Operator | How many operands | True when |
|---|---|---|
| and | two | both are true |
| or | two | at least one is true |
| not | one | its operand is false |
Note that not takes only one operand. It is the only logical operator that reverses rather than combines, which is why it is written in front of its operand rather than between two.
Translation
Each English phrase corresponds to one arrangement of operators.
Match the pairs
Why: Two things are worth noticing. The third could equally be written not (n % 2 == 0), which is longer and says the same thing — the != operator already contains the negation. And the fourth needs the full comparison on both sides of or: writing n % 2 == 0 or 5 would be legal Python meaning something quite different, because 5 on its own counts as true.
Counterexample
The book says the meaning is similar to English. Find where it is not.
Discussion prompt
Give an everyday sentence where or clearly excludes the both-case, and say what Python's or would answer in the corresponding situation.
Hint: Menus are full of them.
Answer:
Would you like tea or coffee? excludes both. Answering yes, both is not a normal response to that question.
Python's or is inclusive: True or True is True. If a program tested is it raining or is it cold on a day that was both, it would answer True, which is usually what you want.
Where the difference bites is a condition meant to be exclusive — exactly one of these — which needs to be written out rather than assumed: (a and not b) or (b and not a). Knowing that Python's or does not mean that saves a class of quiet bug.
Section
Section 5
Concept
You now have all the pieces an if statement needs: operators that produce numbers you can compare, operators that turn those comparisons into booleans, and operators that combine booleans. Building a condition means using all three layers in order.
year = 2024
by_4 = year % 4 == 0
by_100 = year % 100 == 0
by_400 = year % 400 == 0
is_leap = by_4 and (not by_100 or by_400)| Expression | Which layers it uses | Value for 2024 |
|---|---|---|
| year % 4 == 0 | modulus, then comparison | True |
| year % 100 == 0 | modulus, then comparison | False |
| year % 400 == 0 | modulus, then comparison | False |
| by_4 and (not by_100 or by_400) | logical operators combine them | True |
Notice that the three intermediate results are stored in named variables. A condition assembled from named parts is far easier to check than the same logic written as one long expression, and the names document what each part means.
Think Python, 2nd edition — Allen B. Downey §5.1-5.3, pp. 39-40
Picture it
Arithmetic produces numbers; comparisons produce booleans; logical operators combine them.
Figure (svg): A three-stage diagram showing arithmetic feeding comparisons feeding logical operators
Getting the layers in the wrong order is the commonest way a condition goes wrong: writing year % 4 as a condition on its own uses only the first layer, and the truthiness rule then makes it mean year is not a multiple of four, which is backwards.
Worked example
Assemble it piece by piece, checking each part before combining.
>>> n = 12
>>> n % 2 == 0
True
>>> n % 3 == 0
True
>>> n % 2 == 0 and n % 3 == 0
True| Sub-condition | What it asks | Value |
|---|---|---|
| n % 2 == 0 | is it even? | True |
| n % 3 == 0 | is it a multiple of 3? | True |
| both together | and requires both | True — so n is a multiple of 6 |
Check each part alone first.
Why: At the prompt this costs nothing and it localises any mistake to one comparison rather than to a compound expression.
Combine only once the parts are right.
Why: The and operator works on the two boolean results, so if either part was wrong the combination cannot be right.
Notice what the combination means.
Why: Divisible by 2 and by 3 is exactly divisible by 6, which is a sanity check you can apply independently.
Figure (svg): The state of the program after each line of Worked example building a condition in named steps, drawn as a ladder with one rung per traced line
True, and it means n is a multiple of six. Building the condition in parts made each step checkable at the prompt.
Verify: Test a number that satisfies only one part.
Why: With n = 9 the first part is False and the whole condition is False; with n = 8 the second part fails and it is again False. Checking both single-failure cases confirms the and is doing its job rather than accidentally reporting one side.
Prediction
Three layers. Evaluate them in order.
n = 15
n % 3 == 0 and n % 5 == 0| Sub-expression | Value | Result |
|---|---|---|
| n % 3 | 15 divided by 3 leaves 0 | 0 == 0 is True |
| n % 5 | 15 divided by 5 leaves 0 | 0 == 0 is True |
| True and True | and needs both | True |
Predict first
What is the value of this expression?
Correct: True — fifteen is divisible by three and by five, so both comparisons hold.
Why: Both remainders are zero, so both comparisons produce True, and the and operator gives True. This condition is a test for divisibility by fifteen, which is a useful independent check: 15 % 15 is indeed 0. Building a compound condition and then verifying it against a simpler equivalent is a good habit whenever the logic is not obvious.
Worked example
This runs, produces a value, and means the opposite of what it says.
>>> n = 7
>>> n % 2
1
>>> n % 2 == 0
False| Expression | What it produces | What it means as a condition |
|---|---|---|
| n % 2 | the remainder, a number | 1 |
| as a condition | 1 counts as true | true for ODD numbers |
| n % 2 == 0 | the comparison | False for odd numbers — correct |
Look at what the bare modulus produces.
Why: A number: 1 for odd, 0 for even. Not a boolean at all.
Apply the truthiness rule.
Why: Any nonzero number is interpreted as True, so n % 2 is true precisely when n is ODD — the opposite of what somebody writing an evenness test intends.
Add the comparison.
Why: n % 2 == 0 turns the number into a boolean that means what it says: True for even numbers.
Figure (svg): Two columns comparing the bare modulus used as a condition with the explicit comparison, showing they are opposites
The bare modulus is true for odd numbers, because a remainder of 1 counts as true. The comparison with zero is what makes it an evenness test, and omitting it silently inverts the meaning.
Verify: Test both an even and an odd number against both forms.
Why: For 8 the bare form gives 0 (false) and the comparison gives True; for 7 they are 1 (true) and False. The two forms disagree on every input, which is as clear a demonstration as possible that the comparison is not optional.
Trap
A student writes a condition as n % 2 and finds it behaves correctly for their test — which happened to be an odd number.
Test one case and generalise
Why: The single test passed, and the expression is shorter.
Every even number now takes the wrong branch, silently. This is a semantic error in the sense of lesson 2b: no message, plausible behaviour, wrong result.
Write the comparison explicitly, so the condition says what it means.
Turn every number into a boolean with a relational operator
Why: n % 2 == 0 for even, n % 2 != 0 for odd. Both read as what they test.
Test both cases whenever a condition has two outcomes
Why: One even number and one odd number would have caught this immediately.
The book's advice about truthiness applies exactly here: the flexibility can be useful, but you might want to avoid it unless you know what you are doing — and a condition that means the opposite of its author's intention is what not knowing costs.
Error analysis
One is correct. Mark what is wrong with the other three.
Annotate
The two that stop the program are the harmless ones. The one that runs is the one to worry about.
Faded example
Two conditions, joined. Write both comparisons in full.
Fill in the blanks
is_single_digit = x > 0 and x < 10
Why: Both conditions must hold for the number to be a positive single digit, so the operator is and. Using or would be True for any positive number at all and for anything below ten including negatives, which is a much weaker claim. Note that both comparisons are written out in full, each with its own x — the shorthand you would use in English is not available here, though the next lesson introduces a chained form that is.
Explain it
This is the error every beginner hits, and the explanation is short if you have lesson 2a.
Discussion prompt
A classmate keeps writing if x = 5 and getting a syntax error. Explain in two sentences why Python rejects it, using the words expression and statement, and then give them a way to remember which is which.
Hint: One of the two produces a value and one does not.
Answer:
Say: a single equals sign is an assignment, which is a STATEMENT and has no value, and an if statement needs an EXPRESSION with a value to test. A double equals sign is a comparison, which produces True or False.
The mnemonic that works for most people: one equals sign does one thing, which is putting a value somewhere. Two equals signs ask a question about two things.
It is worth adding that this error is a gift. Some languages allow assignment inside a condition, where it silently assigns and then tests the assigned value — a bug that is very hard to see. Python's refusal turns a lurking semantic error into an immediate syntax error.
Comparison
Fill the blanks. Each family takes different inputs and produces a different kind of output.
Comparison matrix
| Family | Takes | Produces |
|---|---|---|
| arithmetic: // and % | numbers | numbers |
| relational: == != < > <= >= | two values of comparable types | a bool |
| logical: and, or, not | booleans | a bool |
The middle row is the bridge. It is the only family that takes numbers and produces booleans, which is why every condition has one in it somewhere.
Pattern
Five steps, and the middle one is the one that gets skipped.
Step 3 is the one people skip, and skipping it is how a bare remainder ends up as a condition meaning the opposite of what was intended. A condition should always contain a relational operator.
Python documentation — Built-in Types Built-in Types
Check
The remainder, not a percentage.
>>> 23 % 5
3| Step | What happens | Value |
|---|---|---|
| 23 // 5 | four whole fives fit | 4 |
| 4 * 5 | those account for twenty | 20 |
| 23 - 20 | what is left | 3 |
Check your understanding
What is the value of 23 % 5?
Answer: B
Why: Four whole fives fit into twenty-three, accounting for twenty, and three are left over. The modulus operator returns that remainder. The reconstruction check confirms it: 4 times 5 plus 3 is 23.
Check
One of these is not an operator at all.
Check your understanding
Which of these is NOT valid Python?
Answer: C
Why: There is no such thing as =< or =>. In every two-character relational operator the equals sign comes second, so it is <= and >=. Writing it the other way round is a syntax error rather than an alternative spelling.
Check
Evaluate each side, then apply the rule for the operator.
x = 4
x > 0 and x > 10| Sub-expression | Why | Value |
|---|---|---|
| x > 0 | 4 is greater than 0 | True |
| x > 10 | 4 is not greater than 10 | False |
| True and False | and needs both | False |
Check your understanding
What is the value of this expression?
Answer: B
Why: The first comparison is True and the second is False. The and operator is true only if both sides are true, so one failure is enough to make the whole expression False. Note that both comparisons are written out in full, each mentioning x — which is what the and operator requires.
Real world
Remainders and compound conditions are everywhere once you look.
Discussion prompt
Find two places outside programming where a remainder is the useful part of a division rather than the quotient — and one place where a rule is stated as a compound condition with and, or or not. What would go wrong if the or in your example were read as exclusive?
Hint: Clocks, calendars and packing are all remainder problems.
Answer:
Clocks are the classic: what time is it 50 hours from now is a question about 50 modulo 24. Packing is another: how many items are left over after filling whole boxes.
Compound conditions are everywhere in rules and eligibility criteria. You may enter if you are a member or have a ticket is an inclusive or — being both does not disqualify you, and reading it exclusively would be absurd.
That is why Python's or is inclusive: it matches how rules are actually written, even though conversational English often means the exclusive version. When a rule really does mean exactly one, it says so.
Commit first
Answer, then rate your confidence. This one catches people who have only tested positives.
Predict first
What is the value of -7 // 2?
Correct: -4 — floor division rounds DOWN, which for a negative number means away from zero.
Why: Minus seven over two is -3.5, and rounding down goes to the more negative side, giving -4. The tempting answer is -3, which is what int(-7 / 2) gives, because int chops toward zero. The two operations agree on every positive number and disagree on every negative one, which is why testing only positives leaves this unlearned. The reason floor division rounds this way is consistency with modulus: -4 times 2 is -8, and -8 plus 1 is -7, so the remainder is a clean 1 rather than a negative number.
Explain it
The modulus operator is the one worth being able to motivate.
Discussion prompt
A classmate says the modulus operator seems pointless — why would anyone want a remainder? Give them two concrete uses in under a minute, and make one of them something they would actually write.
Hint: The book gives two, and one of them is about to become very common.
Answer:
First: divisibility. If x % y is zero then x is divisible by y, which is how you test whether a number is even, whether a year is a leap year, or whether something divides evenly into groups.
Second: digits. x % 10 gives the last digit and x % 100 the last two, which is how you take a number apart without converting it to text.
The one they will actually write is the evenness test, n % 2 == 0. It appears in the next lesson as the standard example of a condition, and it is probably the single most common use of the operator in beginner code.
Exit ticket
One honest answer. It decides what the next lesson opens with.
Predict first
Which of these is still least solid for you?
Correct: Whichever you picked is the right answer — this one is for you, not for a mark.
Why: All four are prerequisites for the next lesson rather than ends in themselves. The two operators become routine within a few programs, though the negative-number behaviour of floor division is worth one deliberate experiment. The modulus uses are worth over-learning because they turn up constantly. The single-equals mistake stops being a problem quickly, since it produces an immediate syntax error. And combining conditions is the one that keeps mattering — the next lesson's chained conditionals are largely about when to use and rather than a chain of ifs.
Connect it up
One page, from memory.
Draw it
Draw three horizontal bands, one for each family of operator: arithmetic, relational, logical. Put the operators from this lesson in the right band, and draw arrows from each band to the one above showing what feeds what. Then, below the diagram, write down the one expression from this lesson that produces a number where a boolean was intended, and say in one sentence what it means and what it should have been.
Recap
Two pages, and every ingredient the if statement needs.
| If you remember one thing | It is this |
|---|---|
| From floor division | It rounds DOWN. On negatives that is not the same as chopping. |
| From modulus | A remainder of zero means divisible. That test is everywhere. |
| From relational operators | = assigns, == compares. There is no =<. |
| From logical operators | and needs both; or needs one; both sides must be complete expressions. |
| From truthiness | A bare number as a condition is legal and usually means the opposite of what you wanted. |
The next lesson is what all of this was for: the if statement, its else and elif forms, and how to keep a chain of conditions readable rather than nesting it into unreadability.
Think Python, 2nd edition — Allen B. Downey §5.1-5.3, pp. 39-40 — everything on these slides traces back here
Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.