7a Reassignment, Updating Variables, and the while Statement

This lesson separates assignment from equality once and for all, introduces updating a variable and the initialization it requires, gives the formal flow of execution for a while statement, and asks what it takes to prove that a loop terminates.

Subject: Python · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Lesson 7a Reassignment, Updating Variables, and the while Statement

Title

Python · Chapter 7 — Iteration

§7.1-7.3, pp. 63-65

2. By the end of this lesson you can

Objectives

Five things, each one you can check yourself at an interpreter prompt.

Think Python, 2nd edition — Allen B. Downey §7.1-7.3, pp. 63-65 — the pages these objectives are drawn from

3. Before we start: three kinds of repetition

Warm-up

This is the third mechanism for doing something repeatedly. Name the other two.

Discussion prompt

You have already met two ways of running a block of statements repeatedly, in two different chapters. Name both, and say one thing each is good at.

Hint: One was in a case study; the other was a function calling itself.

Answer:

The for loop, in chapter 4, which repeated a fixed number of times — good when you know the count in advance.

Recursion, in chapter 5, which repeated by calling itself — good when the problem has a naturally recursive definition.

This chapter adds a third, the while statement, which repeats until a condition becomes false. That is the one you reach for when you do not know in advance how many times you will need.

4. The one idea behind this lesson: a loop runs until something changes

Concept

A while loop repeats its body as long as a condition is true. For that to end, the body must change something the condition depends on — which is why this chapter starts by looking carefully at what it means to change a variable.

iteration — The repeated execution of a set of statements, using either recursion or a loop.

The body of the loop should change the value of one or more variables so that the condition becomes false eventually and the loop terminates. Otherwise the loop will repeat forever, which is called an infinite loop.

Figure (svg): A flow chart showing a condition being tested, the body running, and the flow looping back to the condition

Think Python, 2nd edition — Allen B. Downey §7.1-7.3, pp. 63-65

5. Reassignment: assignment is not equality

Section

Section 1

6. Two reasons the equals sign does not mean equals

Concept

It is legal to make more than one assignment to the same variable. A new assignment makes an existing variable refer to a new value, and stop referring to the old one. Because Python uses the equal sign for assignment, it is tempting to read a = b as a claim that a and b are equal — but that interpretation is wrong, for two separate reasons.

>>> x = 5
>>> x
5
>>> x = 7
>>> x
7
LineWhat happensState after
x = 5creates x, pointing at 5x -> 5
x = 7repoints the SAME namex -> 7
the 5no longer referred to by xgone

Think Python, 2nd edition — Allen B. Downey §7.1-7.3, pp. 63-64

7. Picture it: the arrow moves

Picture it

The name stays; what it points at changes. This is the state diagram from lesson 2a, one step later.

Figure (svg): A state diagram showing the name x with an arrow that has moved from five to seven

The book's figure 7.1. One name, one arrow, and the arrow has moved.

Nothing about the 5 was modified. The name simply stopped pointing at it, which is a different thing and matters enormously in chapter 10.

8. Worked example: two variables that stop being equal

Worked example

The book's own example, and it is the one that catches people.

>>> a = 5
>>> b = a
>>> a = 3
>>> b
5
LineWhat happensState after
a = 5a points at 5a -> 5
b = ab points at whatever a points ata -> 5, b -> 5
a = 3a is repointed; b is not mentioneda -> 3, b -> 5

Make them equal.

Why: After the second line, a and b are now equal — both refer to 5.

Change one of them.

Why: The third line changes the value of a but does not change the value of b.

Notice why.

Why: b = a copied the ARROW, not a link to a. Once b had its own arrow, later changes to a had nothing to do with it.

Figure (svg): The state of the program after each line of Worked example two variables that stop being equal, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

b is still 5. Assignment made them equal for a moment, and nothing kept them that way — which is the second of the two reasons assignment is not equality.

Verify: Compare with the mathematical reading.

Why: In mathematics, if a = b then changing a would change b, because the equation is a permanent claim. Here it does not, which is a direct demonstration that the equals sign is doing something else. Lesson 2a's invariant-watch probe showed the same thing and this is the book stating it as a principle.

9. Predict: what is b?

Prediction

One assignment copies an arrow; the other moves one.

a = 10
b = a
a = 20
print(b)
LineWhat happensState
b = ab points at what a points atboth -> 10
a = 20a is repointeda -> 20, b -> 10
print(b)b was never mentioned again10

Predict first

What does this print?

  • 10
  • 20
  • None
  • An error

Correct: 10 — b was given its own arrow to 10, and repointing a afterwards had nothing to do with it.

Why: The second line copied the value, not a connection to a. This is the book's own example of assignment's impermanence: an assignment statement can make two variables equal, but they do not have to stay that way. It becomes a much bigger deal in chapter 10, where the value being pointed at is a list that can itself be changed.

10. Worked example: why 7 = a is not legal

Worked example

The asymmetry, made concrete. Try it both ways round.

>>> a = 7
>>> 7 = a
SyntaxError: cannot assign to literal
LineWhat is on the leftResult
a = 7a name on the leftlegal: the name is repointed
7 = aa literal on the leftSyntaxError
whythere is nowhere to put the value7 is not a name

Recall lesson 3a's exception.

Why: Almost anywhere you can put a value you can put an expression, with one exception: the left side of an assignment has to be a variable name.

Apply it here.

Why: 7 is a literal, not a name, so there is nothing to repoint. Python reports that it cannot assign to a literal.

Draw the conclusion.

Why: In mathematics if a = 7 then 7 = a. In Python one is legal and the other is not, which means the operator is not symmetric and therefore is not equality.

Figure (svg): Two columns contrasting the asymmetry of assignment with the symmetry of the equality operator

One of these two operators behaves like mathematical equality. It is not the one with a single character.

A SyntaxError. The two sides of an assignment mean different things — one names a place and the other produces a value — so swapping them is not a rephrasing but an error.

Verify: Check that == is symmetric, as equality should be.

Why: Both a == 7 and 7 == a are legal and give the same answer. The operator that really does mean equality behaves symmetrically, which is exactly the contrast the argument needs.

11. Trap: reassigning so often that the code cannot be read

Trap

The trap

A variable called value is reassigned nine times in twenty lines, each time to something conceptually different.

Reuse a name because it is convenient

Why: Reassignment is legal, and inventing new names feels wasteful.

Now no reader can say what value holds at any given line without tracing the whole function, and the name documents nothing.

The fix

Reassign deliberately, and mostly to update rather than to repurpose.

Reassign when the new value is the same THING, changed

Why: A running total, a current estimate, a loop counter. The name still describes what it holds.

Use a new name when the new value is a different thing

Why: Names are free, and one name per concept is what makes a function readable.

The book puts it as a caution rather than a prohibition: reassigning variables is often useful, but you should use it with caution — if the values of variables change frequently, it can make the code difficult to read and debug.

12. Two truths and a lie: assignment versus equality

Two truths and a lie

Two are true. Keep the lie.

Eliminate the wrong options

Rule out the two true statements.

  • A. a = 7 is legal but 7 = a is not
  • B. Two variables made equal by assignment can stop being equal
  • C. After b = a, changing a also changes b

Survives elimination: C

Why: C is the lie, and it is the misreading the whole section exists to correct. b = a gives b its own reference to the current value; it does not link b to a. Changing a afterwards repoints a alone. If the assignment created a lasting link, the impermanence argument would fail and the equals sign really would mean equality.

13. Watch the state: a name being repointed

Invariant

Step through and watch which arrows move.

Step through it

After the fourth line, how many names exist and what does each refer to?

  1. x is created and points at 5. Nothing else exists.
  2. x is repointed at 7. The 5 is no longer referred to by anything.
  3. y is created and points at whatever x points at, which is 7. Two names, one value.
  4. x is repointed at 9. y still points at 7, because y was never mentioned.

Two names: x refers to 9 and y to 7. Each assignment moved exactly one arrow — the one belonging to the name on its left — which is the whole mechanism.

14. Explain it yourself: two arguments, not one

Explain it to yourself

The book gives two independent reasons. Make sure you can give both.

Discussion prompt

State the two reasons the book gives for why a = b is not a claim of equality, and explain why one of them alone would not be enough.

Hint: One is about direction; the other is about time.

Answer:

First, equality is symmetric and assignment is not: a = 7 is legal and 7 = a is a syntax error.

Second, equality is permanent and assignment is not: a = b can make two variables equal and they need not stay that way.

Neither alone settles it. A language could have an asymmetric equality operator, or a symmetric assignment that was still temporary — so the two arguments rule out different alternatives. Together they establish that the operator is doing something other than asserting a relationship, which is exactly what lesson 2a's becomes reading captured.

15. Updating a variable, and initializing it first

Section

Section 2

16. The kind of reassignment a loop needs

Concept

A common kind of reassignment is an update, where the new value of the variable depends on the old. It is the mechanism every loop is built on.

update — An assignment in which the new value of a variable depends on its old value.

>>> x = 0
>>> x = x + 1
>>> x
1
LineWhat happensNote
x = 0initializationx -> 0
x = x + 1read x, add one, repoint xx -> 1
without the first linethere is no old value to readNameError

This means: get the current value of x, add one, and then update x with the new value. Adding 1 is called an increment; subtracting 1 is called a decrement.

Think Python, 2nd edition — Allen B. Downey §7.1-7.3, pp. 64-64

17. Picture it: read, compute, repoint

Picture it

Three steps in one line, and they happen in this order.

Figure (svg): A ladder showing x equals x plus one being evaluated as the old value plus one and then assigned

The right side is evaluated with the OLD value, which is why the variable must already exist.

This is lesson 2a's read-then-write rule, and it is why an update needs an initialization: the read happens first, and there must be something to read.

18. Worked example: updating a variable that does not exist

Worked example

Predict the error before advancing. The reason for it is the read-then-write rule.

>>> x = x + 1
NameError: name 'x' is not defined
StepWhat happensResult
evaluate x + 1look up xthere is no x
the errorNameErrorbefore any assignment happens
the fixinitialize x firstx = 0

Recall the order.

Why: Python evaluates the right side before it assigns a value to x — which is exactly lesson 2a's rule.

See why that causes the error.

Why: Evaluating x + 1 requires looking up x, and x does not exist yet. The failure happens before the assignment is even attempted.

State the fix.

Why: Before you can update a variable, you have to initialize it, usually with a simple assignment.

Figure (svg): The state of the program after each line of Worked example updating a variable that does not exist, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

A NameError, because the right-hand side is evaluated first and there is nothing to read. The fix is an initialization: assign a starting value before the first update.

Verify: Check that the error message names x rather than the addition.

Why: It says name 'x' is not defined, which points at the lookup rather than at the arithmetic. The message is telling you which of the three steps failed, and it is the first one.

19. Predict: what does this print?

Prediction

The initialization is in the wrong place.

n = 1
while n <= 3:
    total = 0
    total = total + n
    n = n + 1
print(total)
PassWhat happenstotal after
pass 1total reset to 0, then 0 + 1total 1
pass 2total reset to 0, then 0 + 2total 2
pass 3total reset to 0, then 0 + 3total 3

Predict first

What does this print?

  • 6
  • 3
  • 0
  • 1

Correct: 3 — total is reset to zero at the start of every pass, so it only ever holds the last value added.

Why: The initialization is inside the body, so it runs once per pass rather than once in total. The accumulation is destroyed each time and only the final addition survives. Note the characteristic symptom: the answer equals the last item rather than the sum, which is the fingerprint of this bug.

20. Worked example: initialization decides the answer

Worked example

The same loop, two starting values. Both are correct programs.

total = 0
n = 1
while n <= 4:
    total = total + n
    n = n + 1
StageWhat happensState
starttotal = 0, n = 1initialized
pass 1total = 0 + 1 = 1; n = 2total 1
pass 2total = 1 + 2 = 3; n = 3total 3
pass 3total = 3 + 3 = 6; n = 4total 6
pass 4total = 6 + 4 = 10; n = 5condition now false

Notice there are two variables being updated.

Why: total accumulates the sum and n counts the passes. Both are initialized before the loop and both are updated inside it.

Notice what each initialization means.

Why: total starts at 0 because an empty sum is zero. n starts at 1 because that is the first number to add.

Notice which update makes the loop end.

Why: The update to n. Without it the condition would never become false, however correct the total was.

Figure (svg): A state diagram showing total and n after each pass of the loop, ending with total ten and n five

One variable accumulates; the other counts. The condition watches the second.

10, the sum of 1 to 4. Two variables are updated on every pass: one accumulates the answer and one drives the loop toward termination.

Verify: Change the initialization of total to 100 and predict the result.

Why: 110 — the loop adds the same 10 to a different starting point. That the initialization changes the answer without changing the loop is what makes it a genuine part of the algorithm rather than boilerplate.

21. Trap: initializing inside the loop

Trap

The trap

A student puts total = 0 inside the loop body rather than before it.

Group all the setup with the code that uses it

Why: It looks tidier to have the variable created next to where it is updated.

Now total is reset to zero on every pass, so it never accumulates anything and ends up holding only the last value added.

The fix

Initialize before the loop; update inside it.

Ask how many times each line should run

Why: The initialization should run once. Anything inside the body runs once per pass.

Check the answer against a case you can compute

Why: Summing 1 to 4 should give 10. Getting 4 is the signature of an initialization inside the loop.

The symptom is characteristic: a total that always equals the last item, or a counter that is always 1. Both mean an initialization that is being re-run.

22. Discriminate: before the loop or inside it?

Discrimination

Ask how many times each line should run.

Sort into buckets

For a loop that sums the numbers 1 to n, where does each line belong?

before the loop, or after it
total = 0; i = 1; print(total); while i <= n:
inside the body
total = total + i; i = i + 1
before
Each of these should run exactly once. The two initializations set up the starting state, the while header is the loop itself rather than part of its body, and the print reports the finished answer.
inside
Each of these should run once per pass: one accumulates the total and one advances the counter toward the condition becoming false.

23. Complete it: initialize before updating

Faded example

The update needs something to read.

Fill in the blanks

count = 0
while n < 5:
count = count + 1
n = n + 1

Why: Without the initialization, the first pass evaluates count + 1, looks up count, and raises a NameError — because Python evaluates the right side before assigning. Note that n also needs initializing, which the template assumes has happened above: a loop with two updated variables needs two initializations, and forgetting either one produces the same error.

24. Match each term to what it names

Matching

Four words from this section, used precisely throughout the book.

Match the pairs

  • a. reassignment
  • b. update
  • c. increment
  • d. initialization
  • r1. assigning a new value to a variable that already exists
  • r2. an assignment whose new value depends on the old
  • r3. an update that adds one
  • r4. the first assignment, which makes an update possible

Why: The four are nested: an initialization is a plain assignment, an update is a reassignment that reads the old value, and an increment is a particular update. Being precise about them matters because the error messages differ — forgetting the initialization gives a NameError, while getting the update wrong gives a silently wrong answer.

25. The while statement

Section

Section 3

26. Repeating while a condition holds

Concept

Computers are often used to automate repetitive tasks, and repeating identical or similar tasks without making errors is something computers do well and people do poorly. The while statement is Python's way of expressing that directly.

def countdown(n):
    while n > 0:
        print(n)
        n = n - 1
    print('Blastoff!')
LineIts roleNote
n > 0the condition, checked before each passtrue or false
print(n)the bodydisplays the current n
n = n - 1the updatemoves toward the condition failing
print('Blastoff!')not indentedruns after the loop ends

You can almost read the while statement as if it were English: while n is greater than 0, display the value of n and then decrement n. When you get to 0, display the word Blastoff.

Think Python, 2nd edition — Allen B. Downey §7.1-7.3, pp. 64-65

27. Picture it: the same countdown, two ways

Picture it

Chapter 5 wrote this with recursion. Compare the two.

Figure (svg): Two columns comparing the recursive countdown with the while loop version

Same output. The recursion remembers where it is on the stack; the loop remembers in a variable.

That is the essential difference between the two mechanisms, and it explains why deep recursion runs out of stack while a loop does not.

28. Worked example: tracing the loop

Worked example

Apply the three steps literally, one pass at a time.

def countdown(n):
    while n > 0:
        print(n)
        n = n - 1
    print('Blastoff!')
Value of n at the testThe conditionWhat happens
n = 33 > 0 is trueprint 3, n becomes 2
n = 22 > 0 is trueprint 2, n becomes 1
n = 11 > 0 is trueprint 1, n becomes 0
n = 00 > 0 is falseleave the loop, print Blastoff

Test before every pass, including the first.

Why: Step 1 of the flow of execution is to determine whether the condition is true or false, and it happens before the body runs at all.

Run the body, then loop back.

Why: If the condition is true, run the body and then go back to step 1. The test happens again before the next pass.

Leave when the condition fails.

Why: If false, exit the while statement and continue execution at the next statement — which here is the un-indented print.

Figure (svg): A flow chart showing the countdown loop testing n, printing, decrementing, and looping back until the condition fails

3, 2, 1, Blastoff — the same output as the recursive version, produced by four tests and three passes through the body.

Verify: Count the tests and the passes.

Why: Four tests and three passes: the condition is checked one more time than the body runs, because the last check is the one that ends the loop. That off-by-one is not an error — it is the shape of a while loop, and expecting it prevents a whole family of confusions.

29. Predict: how many times does the body run?

Prediction

The condition is tested before each pass, including the first.

n = 3
while n > 0:
    print(n)
    n = n - 1
TestResultConsequence
test with n = 3truepass 1
test with n = 2truepass 2
test with n = 1truepass 3
test with n = 0falseleave

Predict first

How many times does the body run, and how many times is the condition tested?

  • Three passes, three tests
  • Three passes, four tests
  • Four passes, four tests
  • Four passes, three tests

Correct: Three passes and four tests — the condition is checked once more than the body runs, because the final check is the one that ends the loop.

Why: Step 1 of the flow of execution happens before every pass and once more at the end. That extra test is easy to forget and it matters when the condition has a side effect or is expensive to evaluate. It is also why a loop can run zero times: the very first test may already be false.

30. Worked example: a loop whose body never runs

Worked example

The condition is tested before the first pass. Work out what that allows.

>>> countdown(0)
Blastoff!
>>> countdown(-5)
Blastoff!
ArgumentThe first testPasses
n = 00 > 0 is false immediatelythe body never runs
n = -5-5 > 0 is false immediatelythe body never runs
outputjust Blastoffzero passes

Apply step 1 before anything else.

Why: The condition is determined first, so a loop whose condition is false at the start runs its body zero times.

Notice this is not an error.

Why: Zero passes is a perfectly ordinary outcome, and it is often the right one — a loop over an empty collection should do nothing.

Connect it to the termination proof.

Why: In the case of countdown we can prove the loop terminates: if n is zero or negative, the loop never runs.

Figure (svg): The state of the program after each line of Worked example a loop whose body never runs, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

Both print only Blastoff. Because the condition is tested before the first pass, a while loop can run its body zero times — which is exactly what makes countdown safe for negative arguments.

Verify: Compare with the recursive version from lesson 5c.

Why: That version also handled negatives, because its base case tested n <= 0 rather than n == 0. Both mechanisms need the same care about the boundary, and both get it right here for the same reason: the check is an inequality rather than an equality.

31. Trap: a body that never changes the condition

Trap

The trap

A loop counts down but the programmer forgets the line that decrements the counter.

Write the body for what it should DO

Why: Printing the value is the visible work; the decrement is bookkeeping and easy to omit.

The condition is checked, found true, checked again, found true — forever. The program prints the same number until it is killed.

The fix

The body must change something the condition depends on.

After writing the body, find the variable in the condition

Why: Then check that the body assigns to it, in a direction that moves toward failure.

Prove it if you can

Why: For countdown: if n is zero or negative the loop never runs, and otherwise n gets smaller each time, so eventually it reaches 0.

The book states the requirement directly: the body of the loop should change the value of one or more variables so that the condition becomes false eventually and the loop terminates. Otherwise the loop will repeat forever, which is called an infinite loop.

32. Rank: the three steps of a while statement

Ranking

The book states them formally. Put them in order.

Put in order

  1. determine whether the condition is true or false
  2. if false, exit the loop and continue at the next statement
  3. if the condition is true, run the body
  4. go back to the first step

Why: Test first, then the false case, then the true case, then loop back. Putting the test anywhere but first would change the meaning: a loop whose body ran before the first test would always run at least once, which is a different construct that some languages provide separately. Python's while always tests first.

33. Compare: three ways to repeat

Comparison

Fill the blanks. You have now met all three.

Comparison matrix

MechanismHow it repeatsWhen to reach for it
for looponce per item in a range or sequencewhen you know how many times in advance
recursiona function calls itself, one frame per levelwhen the problem has a recursive definition
while loopuntil a condition becomes falsewhen you do not know the number of passes in advance

The bottom row is what this chapter adds. Newton's method in the next lesson is the clearest case: you cannot know in advance how many improvements it will take.

34. Think it through: why test before the body?

Socratic

Some languages offer a loop that tests afterwards. Compare them.

Discussion prompt

Python's while tests the condition before running the body, so the body may run zero times. What would be different if it tested afterwards, and which behaviour is more often what you want?

Hint: Think about looping over something that might be empty.

Answer:

Testing afterwards would guarantee at least one pass, so a loop over an empty collection would process a nonexistent item — which is nearly always wrong.

Testing first handles the empty case correctly with no extra code, and that case is extremely common: an empty list, a file with no lines, a countdown from zero.

Which is why countdown(0) prints only Blastoff and needs no special handling. The zero-pass case being natural rather than exceptional is a real design benefit, and it is why test-first is the default in most languages that offer both.

35. Proving that a loop terminates

Section

Section 4

36. Sometimes you can, and sometimes nobody can

Concept

In the case of countdown we can prove that the loop terminates: if n is zero or negative the loop never runs, and otherwise n gets smaller each time through the loop, so eventually we have to get to 0. For some other loops it is not so easy to tell.

def sequence(n):
    while n != 1:
        print(n)
        if n % 2 == 0:
            n = n / 2
        else:
            n = n*3 + 1
CaseWhat the body doesEffect on n
n evenhalve itn gets smaller
n oddtriple it and add onen gets BIGGER
terminationn sometimes increases and sometimes decreasesno obvious proof

The condition is n != 1, so the loop continues until n is 1. Since n sometimes increases and sometimes decreases, there is no obvious proof that n will ever reach 1. For a starting value of 3 the sequence is 3, 10, 5, 16, 8, 4, 2, 1.

Think Python, 2nd edition — Allen B. Downey §7.1-7.3, pp. 65-65

37. Picture it: a sequence that goes up before it comes down

Picture it

Starting from 3, the value more than triples before it starts shrinking.

Figure (svg): The Collatz sequence starting at three drawn as a row of boxes: three, ten, five, sixteen, eight, four, two, one

The highlighted values are increases. The sequence rises twice before reaching 1.

The tail of that sequence — 16, 8, 4, 2, 1 — is a power of two, and once you reach one the rest is guaranteed: it will be even every time until it reaches 1.

38. Worked example: proving countdown terminates

Worked example

A real proof, in two cases, and it is short.

def countdown(n):
    while n > 0:
        print(n)
        n = n - 1
CaseArgumentConclusion
case 1: n <= 0the condition is false at oncezero passes; terminates
case 2: n > 0n decreases by exactly 1 each passmust reach 0
conclusionboth cases terminateproved

Split into cases that cover every input.

Why: Either n starts at zero or below, or it starts above zero. Nothing else is possible.

Handle the easy case.

Why: If n is zero or negative, the loop never runs — so it certainly terminates.

Handle the other.

Why: Otherwise n gets smaller each time through the loop, by a fixed amount, so eventually we have to get to 0.

Figure (svg): The state of the program after each line of Worked example proving countdown terminates, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

The loop terminates for every integer n. The proof is two cases and one observation: the value decreases by a fixed amount toward a fixed target.

Verify: Check whether the proof survives if n starts as a float.

Why: For n = 2.5 the values go 1.5, 0.5, -0.5 — and the condition n > 0 does become false, so it still terminates. The proof survives because the condition is an inequality, unlike lesson 6c's factorial, whose == 0 test a float could step past. Inequalities are more robust than equalities for exactly this reason.

39. Predict: the next few values

Prediction

Apply the rule: halve if even, triple and add one if odd.

Predict first

Starting from 6, what are the next three values in the sequence?

  • 3, 10, 5
  • 12, 6, 3
  • 3, 1, 4
  • 5, 16, 8

Correct: 3, 10, 5 — six is even so it halves to 3, three is odd so it becomes 10, and ten is even so it halves to 5.

Why: Notice the increase at the second step: 3 becomes 10, more than tripling. That is exactly why the countdown termination proof does not apply — there is no quantity that decreases on every pass. From 6 the full sequence is 6, 3, 10, 5, 16, 8, 4, 2, 1, which takes eight passes to reach a value that started smaller.

40. Worked example: the loop nobody can prove

Worked example

The book includes it deliberately. Work out where the argument breaks down.

def sequence(n):
    while n != 1:
        print(n)
        if n % 2 == 0:
            n = n / 2
        else:
            n = n*3 + 1
Starting valueThe sequenceVerdict
start 33, 10, 5, 16, 8, 4, 2, 1terminates after 7 passes
start 1616, 8, 4, 2, 1a power of two: always halved
start anythingnobody has proved it terminatesan open problem

Try the countdown proof and watch it fail.

Why: That proof relied on the value decreasing every pass. Here it increases whenever n is odd, so there is no decreasing quantity to argue from.

Find a case that can be proved.

Why: If the starting value is a power of two, n will be even every time through the loop until it reaches 1. That is a real proof for a restricted set of inputs.

State the open question.

Why: The hard question is whether we can prove that this program terminates for all positive values of n. So far, no one has been able to prove it or disprove it.

Figure (svg): Two columns comparing a loop whose termination can be proved with one whose termination is an open problem

The left column has a quantity that always moves the same way. That is what a termination proof needs.

It terminates for every value anybody has tried, and no proof exists for all of them. The countdown argument fails because n does not decrease monotonically.

Verify: Check the claim about powers of two on 16.

Why: 16, 8, 4, 2, 1 — even at every step, halved each time, reaching 1 in four passes. That special case is provable by exactly the countdown argument, which shows what the general case is missing: a quantity that always moves the same way.

41. Trap: assuming a loop terminates because it always has

Trap

The trap

A loop is tested on a hundred inputs, terminates on all of them, and is declared safe.

Treat evidence as proof

Why: A hundred successes is genuinely good evidence, and for most loops it would be enough.

The Collatz sequence terminates for every value ever tested — billions of them — and remains unproved. Evidence and proof are different things, and a loop can hide an unbounded case.

The fix

Look for a quantity that changes monotonically toward the condition failing.

Identify what the condition depends on

Why: Then ask whether the body always moves it in one direction.

If it does, you have a proof; if it does not, you have a risk

Why: Which may be acceptable, but it should be known rather than assumed.

Most loops you write will be the easy kind, with a counter that only ever increases or decreases. Noticing when a loop is NOT of that kind is the skill, and the Collatz example exists to make that distinction memorable.

42. Discriminate: can you prove this loop terminates?

Discrimination

Look for a quantity that always moves toward the condition failing.

Sort into buckets

For each loop, is termination easy to prove?

easy to prove it terminates
while n > 0: n = n - 1; while n != 1: n = n // 2 (n starts above 1); while x < 100: x = x + 5; while n > 1: n = n / 2
does not terminate, or nobody can prove it
while n != 1: halve if even, else 3n+1; while x != 0: x = x - 2 (x starts odd)
prov
Each has a quantity that moves in one direction toward the condition failing, by a fixed or shrinking amount. Repeated halving reaches 1; repeated subtraction reaches 0; repeated addition reaches 100.
no
One is the Collatz sequence, whose value sometimes increases and whose termination is an open problem. The other subtracts two from an odd number and tests for exact equality with zero, so it steps past its target forever — lesson 6c's failure, in loop form.

43. Find the counterexample: does decreasing guarantee termination?

Counterexample

countdown's proof relied on n decreasing. Test whether that is enough on its own.

Discussion prompt

Construct a loop in which the variable decreases on every single pass and which nonetheless never terminates. What does your example show about the countdown proof?

Hint: It must decrease by a fixed amount, not merely decrease.

Answer:

while x > 0: x = x / 2 — starting from 1, x becomes 0.5, 0.25, 0.125 and so on. It decreases every pass and never reaches zero.

So decreasing is not enough. The countdown proof relied on decreasing by a FIXED amount toward the target, which guarantees arrival in a finite number of steps.

This matters in practice: loops that halve a value or shrink an interval are extremely common, and they terminate only because they test an inequality against a tolerance rather than waiting for an exact value. The next lesson's Newton's method is exactly such a loop, and its termination test is the subject of a whole section.

44. Where termination proofs matter

Real world

Most of the time you can eyeball it. Sometimes you cannot.

Discussion prompt

Think of a real process that repeats until something is true — searching, refining, retrying. What would happen if the condition were never met, and how do real systems protect against that?

Hint: Consider a program retrying a network request.

Answer:

It would repeat forever, consuming resources and never reporting a problem. A retry loop with no limit is the classic case, and it turns a temporary failure into a permanent hang.

Real systems add a second condition: retry until it succeeds OR until a limit is reached. That converts a loop whose termination depends on the world into one whose termination is provable.

Which is the general technique when you cannot prove termination from the logic: add a counter and a maximum. It is not elegant, and it turns an unbounded risk into a bounded one — which is exactly the trade Python itself makes with its recursion limit.

45. Putting it together: writing a loop that finishes

Section

Section 5

46. Four parts, and all four are needed

Concept

Every correct while loop has the same four parts, and the failures of this lesson are each a missing one: initialize, test, work, update.

total = 0          # initialize
n = 1              # initialize
while n <= 10:     # test
    total = total + n   # work
    n = n + 1           # update
print(total)
PartWhere and how oftenWhy it is needed
initializebefore the loop, oncegives the update something to read
testbefore every passdecides whether to continue
workinside, once per passwhat the loop is for
updateinside, once per passmoves toward the test failing

Leaving out the initialization gives a NameError. Leaving out the update gives an infinite loop. Putting the initialization inside gives a silently wrong answer. Each failure has its own characteristic symptom.

Think Python, 2nd edition — Allen B. Downey §7.1-7.3, pp. 64-65

47. Picture it: the four parts and their failures

Picture it

Each omission has its own signature.

Figure (svg): Two columns listing the four parts of a loop against the failure caused by omitting each

Four parts, four distinct symptoms. The symptom tells you which part is missing.

Diagnosing by symptom is faster than reading, which is the same principle as the error taxonomy in lesson 2b.

48. Worked example: rewriting print_n as a loop

Worked example

The book's exercise: take a recursive function from chapter 5 and make it iterative.

# recursive, from lesson 5c:
#   def print_n(s, n):
#       if n <= 0: return
#       print(s)
#       print_n(s, n-1)

def print_n(s, n):
    while n > 0:
        print(s)
        n = n - 1
Recursive partWhat it wasWhat it becomes
the base caseif n <= 0: returnbecomes the loop condition n > 0
the workprint(s)unchanged
the recursive callprint_n(s, n-1)becomes the update n = n - 1

Turn the base case into the loop condition, reversed.

Why: The recursion stops when n <= 0, so the loop continues while n > 0. A base case and a loop condition are opposites of each other.

Keep the work unchanged.

Why: print(s) does the same job in both versions.

Turn the recursive call into an update.

Why: Calling with n-1 becomes assigning n - 1 to n. Both mean go round again with a smaller n.

Figure (svg): Two columns mapping the parts of a recursive function onto the parts of the equivalent loop

Three parts each, and they correspond exactly.

A three-line loop that does what the four-line recursion did. The base case became the negated condition, and the recursive call became the update.

Verify: Check both versions on n = 0 and n = 3.

Why: Both print nothing for 0 and three lines for 3. The translation is only correct if it agrees on the boundary as well as the ordinary case, and n = 0 is the boundary here — which the loop handles by testing before the first pass.

49. Error analysis: a loop that never ends

Error analysis

Mark what is wrong and say what the symptom would be.

Annotate

  • Both variables are initialized before the loop, so there is no NameError. That rules out one of the three failure modes.
  • The condition tests i, and the body contains an assignment — but the assignment is to count, not to i.
  • So i never changes. The condition i < 5 is true on the first test and true on every subsequent test, forever.
  • The symptom is a program that hangs, printing 0 repeatedly. The repeated identical output is the giveaway: a value that never changes means an update that is not happening.
  • count increases diligently the whole time, which is why the loop looks like it is doing something. An update alone is not enough — it has to be an update to the variable the condition depends on.
  • The fix is to add i = i + 1, or to change the condition to test count.

This is the commonest infinite loop there is, and the check that prevents it takes two seconds: name the variable in the condition, then find an assignment to it in the body.

50. Worked example: diagnosing a loop by its symptom

Worked example

Three broken loops. Name the missing part from the symptom alone.

# symptom A: NameError on the first pass
# symptom B: the program hangs, printing the same value
# symptom C: the total equals the last item, not the sum
SymptomWhat it meansThe missing part
Athe update read a variable that does not existmissing initialization
Bthe condition never becomes falsemissing update
Cthe accumulator is reset every passinitialization inside the loop

Read symptom A.

Why: A NameError on the first pass means the right-hand side of an update found nothing to read, which is a missing initialization.

Read symptom B.

Why: Repeating forever with an unchanging value means the body is not changing what the condition depends on.

Read symptom C.

Why: A total equal to the last item means the accumulator was reset each pass, which is an initialization in the wrong place.

Figure (svg): The state of the program after each line of Worked example diagnosing a loop by its symptom, drawn as a ladder with one rung per traced line

The whole run at once: each drop is one line of the program.

Three symptoms, three different missing parts, all diagnosable without reading the code. Each failure mode has a signature.

Verify: Check that the three symptoms really are distinguishable.

Why: One is an error, one is a hang, and one is a wrong answer — which are the three categories from lesson 2b's error taxonomy. A loop can fail in all three ways, and knowing which you have narrows the search before you read a line.

51. Trap: updating the wrong variable

Trap

The trap

A loop tests one variable and the body updates a different one, usually because of a copy-and-paste.

Check that the body contains an update, without checking which variable

Why: There is an assignment in there, and it looks like the bookkeeping line.

The condition's variable never changes, so the loop runs forever — while some other variable counts diligently upward.

The fix

Match the update to the condition explicitly.

Name the variable the condition tests

Why: Then find an assignment to THAT variable in the body.

Check the direction as well as the variable

Why: Incrementing a variable in a condition that requires it to decrease is as bad as not updating it.

This is a two-second check and it catches the commonest cause of an infinite loop. It is worth doing every time you write a while statement, before you run it.

52. Predict: what does this loop print?

Prediction

All four parts are present. Trace it.

n = 5
result = 1
while n > 1:
    result = result * n
    n = n - 1
print(result)
PassWhat happensresult after
n = 5result = 1 * 55, n becomes 4
n = 4result = 5 * 420, n becomes 3
n = 3result = 20 * 360, n becomes 2
n = 2result = 60 * 2120, n becomes 1

Predict first

What does this print?

  • 120
  • 24
  • 20
  • 5

Correct: 120 — this is the factorial of 5, computed iteratively.

Why: The loop multiplies result by 5, 4, 3 and 2, which is 120. Note that it stops at n > 1 rather than n > 0, because multiplying by 1 would change nothing — a small optimisation that also happens to make the loop match the mathematical definition. Compare this with the recursive factorial from lesson 6b: same answer, one frame instead of six.

53. Sort: which part is missing?

Sorting

Diagnose from the symptom.

Sort into buckets

For each symptom, which of the four parts is missing or misplaced?

an initialization problem
NameError on the first pass; the total equals only the last item
an update problem
the program hangs, printing the same value forever; a counter climbs forever while the condition's variable does not
a condition problem
the loop body never runs at all; the answer is one too small
init
One has no initialization at all, so the first update has nothing to read. The other has it in the wrong place, inside the loop, so the accumulator is reset every pass.
upd
Both are the condition's variable not changing — either no update at all, or an update to the wrong variable. The repeated identical output is the signature.
cond
One condition is false at the very first test, so the body never runs. The other is off by one at the boundary, which is the classic cause of an answer that is close but not right.

54. Explain it: why does my loop never end?

Explain it

The commonest question a beginner asks about loops.

Discussion prompt

A classmate's while loop hangs. Without seeing the code, give them the one check to run first, and explain why it catches most cases.

Hint: It involves naming one variable.

Answer:

Say: name the variable in the condition, then look for an assignment to that exact variable in the body. If there is none, that is your bug.

It catches most cases because the requirement is precise — the body must change the value of one or more variables so that the condition becomes false eventually — and the two common failures are no update at all and an update to the wrong variable.

Add the second half of the check: even when there is an update, confirm it moves in the right direction. Incrementing a counter in a loop that runs while the counter is below a limit is fine; incrementing it in a loop that runs while it is above one is an infinite loop with an update in it.

55. Compare: recursion and the while loop

Comparison

Fill the blanks. Both repeat; they differ in where the state lives.

Comparison matrix

QuestionRecursionwhile loop
Where is the current value kept?in each frame's parameterin a variable that is updated
How many frames?one per levelone, always
What stops it?reaching the base casethe condition becoming false
How does it fail?infinite recursion, then a RecursionErroran infinite loop, which never stops on its own

The last row is worth noticing: Python stops an infinite recursion at a thousand frames, and an infinite loop it will run forever. The loop's failure mode is quieter and worse.

56. The procedure: writing a while loop

Pattern

Six steps, and the last two are checks rather than code.

  1. Decide what the loop should keep track of, and give each thing a variable.
  2. Initialize every one of those variables before the loop, with the value it should have before any pass.
  3. Write the condition, in terms of the variable that decides when to stop.
  4. Write the body: the work the loop exists to do, plus the update that moves the condition toward failing.
  5. Check the update: name the variable in the condition, and confirm the body assigns to it in the right direction.
  6. Check the boundary: what happens when the condition is false at the very first test, and what happens on the final pass.

Step 5 takes two seconds and prevents the most common failure. Step 6 catches the off-by-one, which is the most common wrong answer.

Python documentation — An Informal Introduction to Python An Informal Introduction to Python

57. Check yourself 1 of 3: reassignment

Check

One assignment copies a value; it does not create a link.

a = 5
b = a
a = 3
LineWhat happensState
b = ab gets its own reference to 5a -> 5, b -> 5
a = 3a is repointeda -> 3, b -> 5
bnever mentioned againstill 5

Check your understanding

What is the value of b?

  • A. 3
  • B. 5 (correct)
  • C. None
  • D. It is an error to have two names for one value

Answer: B

Why: The second line gave b its own reference to the value 5. The third line repointed a and mentioned b not at all, so b still refers to 5. This is the book's demonstration that assignment is not equality: an assignment statement can make two variables equal, but they do not have to stay that way.

Why A tempts people
This would require b to be linked to a, so that changing one changed the other. Assignment copies the reference at the moment it runs; it creates no lasting connection.
Why C tempts people
Both names refer to real values throughout. Nothing here produces None.
Why D tempts people
Two names referring to one value is completely ordinary — it is what happens on every function call, where a parameter and an argument refer to the same value.

58. Check yourself 2 of 3: the flow of a while statement

Check

The condition is tested before every pass, including the first.

n = 0
while n > 0:
    print(n)
    n = n - 1
print('done')
StepWhat happensOutput
first test0 > 0 is falsethe body never runs
exitcontinue at the next statementprint('done')
outputone linedone

Check your understanding

What does this print?

  • A. 0 and then done
  • B. Just done (correct)
  • C. Nothing
  • D. It loops forever

Answer: B

Why: The condition is determined before the body runs, and it is false immediately, so the loop runs zero times and execution continues at the next statement. A while loop running its body zero times is a normal and often desirable outcome — it is what makes this countdown safe for zero and negative arguments.

Why A tempts people
This would require the body to run once before the condition was checked, which is what a test-after loop would do. Python's while tests first.
Why C tempts people
The final print is not part of the loop body, so it runs regardless of how many passes the loop made.
Why D tempts people
The loop cannot run forever when its condition is false at the first test. An infinite loop needs a condition that stays true.

59. Check yourself 3 of 3: termination

Check

Look for a quantity that moves toward the condition failing.

Check your understanding

Which of these loops is easiest to prove terminates, for any positive integer n?

  • A. while n != 1: halve n if even, else n = 3n + 1
  • B. while n > 1: n = n // 2 (correct)
  • C. while n != 0: n = n - 2
  • D. while n > 0: n = n + 1

Answer: B

Why: Floor division by two strictly decreases any n above 1, and it cannot skip past the target because the condition is an inequality. So n must eventually fall to 1 and the loop ends. The proof has the same shape as countdown's: a quantity that always moves one way toward a target it cannot overshoot.

Why A tempts people
This is the Collatz sequence. It has terminated for every value ever tried and nobody has proved it terminates for all of them — the book includes it precisely as the example that cannot be proved.
Why C tempts people
Subtracting two from an odd number steps past zero forever, and the condition tests exact equality. This is lesson 6c's failure in loop form.
Why D tempts people
This moves away from the condition failing. It is an infinite loop with a perfectly good update in it, which is why the direction of the update matters as much as its presence.

60. Where this shows up outside this course

Real world

Loops that repeat until a condition holds are everywhere, and so are the ones that do not stop.

Discussion prompt

Find a real process that repeats until something is true, and identify its equivalent of the four parts: what is initialized, what is tested, what work is done, and what is updated. Then say what would happen if the update were missing.

Hint: Boiling something until it thickens, or searching a shelf.

Answer:

Stirring a sauce until it thickens: the initialization is putting it on the heat, the test is whether it has thickened, the work is stirring, and the update is the heat continuing to act.

Without the update — the heat off — you would stir forever and the test would never pass. That is an infinite loop, and the analogy is exact.

The useful transfer is the diagnostic: when a repeated process is not finishing, ask what is supposed to be changing and check whether it actually is. That question resolves most stuck loops, in code and out of it.

61. Confidence wager: commit before you check

Commit first

Answer, then rate your confidence. This one is about counting.

Predict first

A while loop runs its body three times before the condition becomes false. How many times is the condition evaluated?

  • Three
  • Four
  • Two
  • It depends on the condition

Correct: Four — once before each of the three passes, and once more at the end, which is the evaluation that ends the loop.

Why: The flow of execution tests first, every time, and the loop only exits when a test comes back false — so there is always exactly one more test than there are passes. This matters whenever the condition is expensive to evaluate or has a side effect, and it is also why a loop can run zero times: the first test may already be false. If you answered three, you were counting the tests that led to a pass and forgetting the one that did not.

62. Explain it to someone else

Explain it

The assignment-is-not-equality argument is worth being able to make properly.

Discussion prompt

A classmate who is good at mathematics is uncomfortable with reassignment: if x was 5, how can it now be 7? Give them the two arguments the book gives, and one line of Python that demonstrates each.

Hint: One demonstration is an error; the other is a surprising value.

Answer:

First, asymmetry: a = 7 is legal and 7 = a is a syntax error. Equality would work both ways round; assignment does not, because one side names a place and the other produces a value.

Second, impermanence: after a = 5 and b = a, setting a = 3 leaves b at 5. Equality would be permanent; assignment is a one-time act.

Then give them the reading that resolves it: the equals sign means becomes, and Python has a separate operator, ==, that really does mean equals. Once they have both operators, the discomfort usually goes.

63. Exit ticket

Exit ticket

One honest answer. It decides what the next lesson opens with.

Predict first

Which of these is still least solid for you?

  • Why assignment is not equality, in both of its senses
  • Updating a variable, and why it must be initialized first
  • The three-step flow of execution of a while statement
  • Proving that a loop terminates, and knowing when you cannot

Correct: Whichever you picked is the right answer — this one is for you, not for a mark.

Why: The assignment argument is conceptual and worth settling once, because it comes back in chapter 10 in a much sharper form when the values are lists. Initialization becomes automatic after two or three loops, and its error message is unusually clear. The three-step flow is worth stating out loud, because the extra final test is where off-by-one errors come from. And termination is the one that keeps mattering — most of your loops will be provably finite, and recognising the ones that are not is a genuinely valuable skill.

64. Synthesis: draw the map of this lesson

Connect it up

One page, from memory.

Draw it

Draw a while loop as a flow chart with the three steps labelled, and mark on it where the condition is tested for the last time. Beside it, list the four parts of a working loop and, next to each, the symptom you would see if it were missing or misplaced. Finally, write the two-case termination proof for countdown, and say in one sentence which step of it the Collatz sequence fails.

65. What you can do now

Recap

Three pages, and the third and last of the book's repetition mechanisms.

If you remember one thingIt is this
From reassignmentb = a copies the arrow. It does not link b to a.
From updatingInitialize before the loop. The right side is read before the left is written.
From whileThe condition is tested one more time than the body runs.
From terminationThe body must change what the condition depends on, in the right direction.
From CollatzTerminating for every value ever tried is not the same as being proved to terminate.

The next lesson finishes the chapter with the break statement, an algorithm that improves an estimate until it stops changing, the reason you must not test floats for equality, and a debugging technique that halves the search each time you use it.

Think Python, 2nd edition — Allen B. Downey §7.1-7.3, pp. 63-65 — everything on these slides traces back here

Sources

  1. Think Python, 2nd edition — Allen B. Downey — Allen B. Downey, Think Python: How to Think Like a Computer Scientist, 2nd edition (Green Tea Press, 2015), §7.1-7.3, pp. 63-65
  2. Python documentation — An Informal Introduction to Python
  3. Python documentation — More Control Flow Tools

Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108