Control flow · about 40 min

Repeating with while

Why this matters

Computers are valuable because they repeat work tirelessly: process every line of a file, retry a failed connection, keep asking until the user gives valid input, run a game until the player quits. A loop runs a block of code many times. Python has two loop statements; while is the more fundamental one: it repeats as long as a condition stays true. It is the right tool whenever you do not know in advance how many repetitions you need.

The while statement

count = 1
while count <= 3:
    print("iteration", count)
    count += 1
print("done")
iteration 1
iteration 2
iteration 3
done

while looks like if: a condition, a colon, an indented block. The difference is what happens after the block. An if continues with the next statement; a while goes back and tests the condition again. Each pass through the block is called an iteration.

            +-------------------+
            v                   |
     condition true? --yes--> block
            | no
            v
     next statement after the loop

Underneath, this is exactly what the bytecode does: test the condition, jump past the block if false, otherwise run the block and jump back to the test.

Tracing a loop

The best way to understand a loop is to trace it by hand, writing down the variables at each test:

test #countcount <= 3action
11Trueprint 1, count becomes 2
22Trueprint 2, count becomes 3
33Trueprint 3, count becomes 4
44Falseleave the loop

Note that the condition is tested one more time than the body runs, and if it is false at the start, the body runs zero times.

The parts of a counting loop

Most loops that count have three parts. Forget any one and the loop misbehaves:

i = 0               # 1. initialize the loop variable
while i < 5:        # 2. test it
    print(i)
    i += 1          # 3. update it, moving towards the end

i += 1 is augmented assignment, short for i = i + 1 (from the expressions module). Remember that for an int it creates a new int object and rebinds i; ints are immutable.

Accumulators

A common pattern keeps a running result in a variable, called an accumulator, updated on each iteration:

n = int(input())
total = 0
i = 1
while i <= n:
    total += i
    i += 1
print(f"1 + ... + {n} = {total}")

With input 100:

1 + ... + 100 = 5050

The accumulator is initialized before the loop to the "empty" value: 0 for a sum, 1 for a product, "" for a string you build up.

Loops that depend on data

A while loop shines when the number of iterations depends on the data. This loop counts how many times a number can be halved before it reaches 1:

n = int(input())
halvings = 0
while n > 1:
    n //= 2
    halvings += 1
print(halvings)

For 1000 it prints 9. Nobody had to know the answer in advance; the loop stops when the condition says so.

Sentinel-controlled input

Programs often read input until a special sentinel value says "stop". The classic structure reads once before the loop and again at the end of each iteration:

total = 0
line = input()
while line != "done":
    total += int(line)
    line = input()
print(f"total: {total}")

With input lines 4, 10, done:

total: 14

Trace it: read 4, test (not done), add, read 10, test, add, read done, test fails, print. The "priming read" before the loop makes sure the condition always has a fresh value to test.

while True with break

The same loop is often written with an infinite loop that exits from the middle, using break (covered fully two lessons from now). break leaves the loop immediately:

total = 0
while True:
    line = input()
    if line == "done":
        break
    total += int(line)
print(f"total: {total}")

This avoids writing input() twice. Both styles are common; choose the one that reads more clearly.

Reading until the input ends

When input comes from a file or pipe, there may be no sentinel; it just ends. input() then raises EOFError. You will learn to handle that with try/except in the errors module, and to read all of stdin with sys.stdin in the input and output lesson.

Validating input in a loop

Loops let a program insist on good input instead of crashing or giving up:

age = int(input())
while age < 0 or age > 150:
    print("Please enter an age between 0 and 150.")
    age = int(input())
print(f"Age accepted: {age}")

The loop body runs only while the input is bad, so valid input on the first try skips it entirely.

Infinite loops

If the condition never becomes false, the loop runs forever. That is sometimes intended (a server waiting for requests, while True: with a break), but usually it is a bug:

i = 0
while i < 5:
    print(i)
# forgot i += 1: prints 0 forever

In a terminal, stop a runaway program with Ctrl-C. The terminal sends the signal SIGINT to the process, and Python turns it into a KeyboardInterrupt exception, which stops the program with a traceback:

^CTraceback (most recent call last):
  File "main.py", line 3, in <module>
    print(i)
KeyboardInterrupt

In learnprog, a program that runs longer than 10 seconds is stopped and reported as "timed out: endless loop, or waiting for input?". When you see that, check that every path through your loop body moves the loop variable toward the end, and that you are not calling input() more often than there are input lines.

Common mistakes

Recap

Going further: run import dis; dis.dis("while i < 3:\n i += 1") and find the backward jump (JUMP_BACKWARD) that makes it a loop. Then look up the Collatz conjecture: a three-line loop that mathematicians still cannot prove always stops.

Exercises

Exercise 1 · write code

Countdown

Read a whole number n (0 or more) and count down from n to 1, one number per line, then print Liftoff!. For input 3:

3
2
1
Liftoff!

For input 0, only Liftoff! is printed. Use a while loop.

File: main.py

Starting point

n = int(input())
while n > 0:
    print(n)
print("Liftoff!")

Example

input

3

expected output

3
2
1
Liftoff!

Example

input

0

expected output

Liftoff!
Hint 1

The starter never changes n, so the loop never ends (the check reports a time-out).

Hint 2

Decrease n inside the loop body: n -= 1.

Reference solution
n = int(input())
while n > 0:
    print(n)
    n -= 1
print("Liftoff!")

Exercise 2 · quiz

Count the iterations

How many times is hello printed?

i = 10
while i < 50:
    print("hello")
    i = i * 2
Answer

Trace it: i is 10 (print, then 20), 20 (print, then 40), 40 (print, then 80), and then 80 < 50 is false, so the loop stops. Three prints. Tracing values of the loop variable in a small table, iteration by iteration, is the most reliable way to understand any loop.

Exercise 3 · write code

Sum until a sentinel

Read whole numbers, one per line, until a line containing 0. Then print how many numbers were entered (not counting the 0) and their sum, exactly in this format:

count: 3
sum: 12

That example is for the input lines 5, -1, 8, 0. The input always ends with a 0 line. If the very first number is 0, print count: 0 and sum: 0.

File: main.py

Starting point

total = 0
count = 0

Example

input

5
-1
8
0

expected output

count: 3
sum: 12

Example

input

0

expected output

count: 0
sum: 0
Hint 1

Read the first number before the loop, and the next one at the end of the loop body.

Hint 2

Loop while n != 0:; inside, add n to the total and add 1 to the count.

Reference solution
total = 0
count = 0
n = int(input())
while n != 0:
    total += n
    count += 1
    n = int(input())
print(f"count: {count}")
print(f"sum: {total}")

Exercise 4 · write code

Digits of a number

Read a non-negative whole number and print how many digits it has and the sum of its digits, using arithmetic only (// 10 and % 10 in a while loop; do not convert the number to a string). For input 9045:

digits: 4
digit sum: 18

The number 0 has 1 digit and digit sum 0.

File: main.py

Starting point

n = int(input())

Example

input

9045

expected output

digits: 4
digit sum: 18

Example

input

0

expected output

digits: 1
digit sum: 0
Hint 1

n % 10 is the last digit; n // 10 removes the last digit.

Hint 2

Repeat until n becomes 0. Be careful with input 0: it still has one digit.

Hint 3

One approach handles 0 with if n == 0: before the loop; another uses while True: and breaks after processing a digit.

Reference solution
n = int(input())
digits = 0
total = 0
while True:
    total += n % 10
    digits += 1
    n //= 10
    if n == 0:
        break
print(f"digits: {digits}")
print(f"digit sum: {total}")

Exercise 5 · write code

Collatz steps

The Collatz process starts with a positive whole number n and repeats: if n is even, divide it by 2; if odd, replace it with 3 * n + 1. It is conjectured (but unproven!) that this always reaches 1.

Read a positive whole number and print the sequence on one line, values separated by single spaces and ending with 1, then on a second line steps: followed by the number of steps taken to reach 1. For input 6:

6 3 10 5 16 8 4 2 1
steps: 8

For input 1 the sequence is just 1 and there are 0 steps. Print each value with print(n, end=" ") except the last one, or build up a string; trailing spaces at the end of a line are ignored by the checker.

File: main.py

Starting point

n = int(input())
steps = 0
print(n)
print("steps:", steps)

Example

input

6

expected output

6 3 10 5 16 8 4 2 1
steps: 8

Example

input

1

expected output

1
steps: 0
Hint 1

Loop while n != 1:.

Hint 2

Use n //= 2 (not /), so n stays an integer and prints without .0.

Hint 3

Count one step each time through the loop.

Reference solution
n = int(input())
steps = 0
line = str(n)
while n != 1:
    if n % 2 == 0:
        n //= 2
    else:
        n = 3 * n + 1
    steps += 1
    line += " " + str(n)
print(line)
print(f"steps: {steps}")