1.2.17 · D4Introduction to Programming (Python)

Exercises — while loop — condition-based, infinite loop dangers

3,607 words16 min readBack to topic

Everything here builds on the parent topic. When a comparison or truth-value shows up, see Boolean expressions and comparison operators; when we count checks, that is an Off-by-one errors question; when we jump out early, that is break and continue statements.


Level 1 — Recognition

"Can you read a loop and say what it does?"

Recall Solution 1.1

The update is i += 2, so i climbs by 2 each round. We trace every check — including the final failing one, exactly like the parent's trace table. The number line below shows i hopping by 2 until it lands on 8 and the door i < 8 shuts.

Figure — while loop — condition-based, infinite loop dangers
check i i < 8? action
1 2 True print 2, i→4
2 4 True print 4, i→6
3 6 True print 6, i→8
4 8 False exit

Prints 2 4 6 — three numbers. The condition is checked 4 times (3 True + 1 final False).

Recall Solution 1.2

A while is a pre-test loop: it checks before the first run. First check is 0 > 5 which is False, so the body runs zero times. Nothing is printed. This is the "the body may run zero times" fact from the parent — the loop can be skipped entirely.

Recall Solution 1.3
  • A — has an update x -= 1 that pushes toward x > 0 becoming false. Terminates.
  • B — the body never changes x. x stays 10, x > 0 is forever True. Infinite loop. (The classic "missing update".)
  • Cx goes 10, 8, 6, 4, 2, 0. At x == 0 the check 0 != 0 is False. Terminates. (It happens to land exactly on 0 because 10 is even — see the L3 trap.)

Answer: B.


Level 2 — Application

"Can you write a straightforward loop from a spec?"

Recall Solution 2.1
i = 1                 # Initialise
while i <= 5:         # Condition
    print(i)
    i += 1            # Update
print("done")

Prints 1 2 3 4 5 then done. All three loop ingredients are present — Initialise (i = 1), Condition (i <= 5), Update (i += 1) — the checklist defined at the top of this page.

Condition checks: i takes values 1,2,3,4,5 (all True) then 6 (False). That is 6 checks to print 5 numbers — the off-by-one final check.

Recall Solution 2.2

An accumulator pattern: one variable holds the running total, another is the counter.

total = 0
i = 1
while i <= 10:
    total += i        # add current i into the running total
    i += 1            # update the counter
print(total)          # 55

The counter i rises 1→10; each round adds it into total. When i reaches 11 the check 11 <= 10 is False → exit. Prints 55.

Recall Solution 2.3

We don't know how many tries → while, not for. The update is the new input().

answer = ""                       # initialise so the condition can be checked once
while answer != "stop":           # compare strings directly
    answer = input("Type something (or 'stop'): ")
print("You stopped.")

answer starts "" so the first check "" != "stop" is True → we enter and ask. Each input() overwrites answer; when the user types stop, "stop" != "stop" is False → exit.


Level 3 — Analysis

"Can you find why a loop misbehaves and predict its exact output?"

Recall Solution 3.1

Infinite loop. Adding 0.1 in binary floating-point never lands exactly on 2.0 — the running value overshoots to something like 2.0000000000000004, so x != 2.0 stays True forever. The printed values (rounded) look like they pass through 2.0, but the stored value never equals it.

Fix: never test floats with ==/!=. Use a boundary comparison:

x = 1.0
while x < 2.0:
    x += 0.1

This is the "float never equals" killer from the parent's danger table.

Recall Solution 3.2

Exact behaviour of the code as written: it prints 0 2 4 6 8 — but by a fragile route, and one tiny change makes it loop forever. Let us trace it honestly rather than guess.

i i < 10? i % 2 == 0? what happens
0 True True i += 1i = 1, continue (nothing printed yet)
1 True False print i - 1 = 0, i += 1i = 2
2 True True i += 1i = 3, continue
3 True False print 2, i += 1i = 4
pattern repeats: prints 4, 6, 8
10 False exit

So the output is exactly 0 2 4 6 8. The point of this exercise: the continue path does carry an update (i += 1 sits before the continue), which is the only reason it terminates. This is the safe way to use continue.

Now the danger — move the update to the wrong side of continue:

i = 0
while i < 10:
    if i % 2 == 0:
        continue          # <-- update was removed from this path
        i += 1            # DEAD CODE: never runs, it's after continue
    print(i - 1)
    i += 1

Trace: i = 0, condition True, 0 % 2 == 0 True → continue jumps straight back to the condition without changing i. i is stuck at 0 forever → infinite loop. See break and continue statements: continue skips everything below it in the body, including any update placed there.

Rule: when a path uses continue, put the update before the continue, never after. The first (working) version does this correctly; the second is the classic "update in the wrong place" infinite loop from the parent's danger table.

Cleaner fix (no continue at all):

i = 0
while i < 10:
    if i % 2 == 0:
        print(i)
    i += 1        # single update on the common path — always runs
Recall Solution 3.3

// is integer (floor) division — it halves and drops any fraction.

check n before n > 1? after n // 2
1 100 True 50
2 50 True 25
3 25 True 12
4 12 True 6
5 6 True 3
6 3 True 1
7 1 False exit

Prints 50 25 12 6 3 1. Condition checked 7 times; loop ends with n = 1.


Level 4 — Synthesis

"Can you combine loops with conditions, break, and input into a small program?"

Recall Solution 4.1

This is the deliberate-infinite pattern from the parent: while True + a controlled break.

while True:                       # intentional infinite loop
    cmd = input("> ")
    if cmd == "quit":
        print("Goodbye!")
        break                     # the real, controlled exit
    elif cmd == "hello":
        print("Hi there!")
    else:
        print("unknown command")

The break is the genuine stop sign. This is not an accidental infinite loop — the exit is explicit and reachable.

Recall Solution 4.2

Two loops: a validation loop (unknown tries) and a computation loop (accumulator).

n = 0
while n <= 0:                     # validation: re-ask on bad input
    n = int(input("Positive integer: "))   # type conversion, see input() note
 
fact = 1
i = 1
while i <= n:
    fact *= i                     # accumulate the product
    i += 1
print(fact)

For n = 5: fact goes 1·1=1, ·2=2, ·3=6, ·4=24, ·5=120. Prints 120. The validation loop uses int(input(...)) for input() and type conversion; the computation loop is a multiply-accumulator, sibling of Ex 2.2's sum-accumulator.


Level 5 — Mastery

"Can you turn a raw goal into a correct, terminating loop and justify termination?"

Recall Solution 5.1
k = 1
while 2 ** k <= 1000:            # keep going while NOT yet past 1000
    k += 1
print(k)                         # 10

Powers of two: 2,4,8,16,32,64,128,256,512,1024. At k = 10, 2**10 = 1024 > 1000, so the condition 1024 <= 1000 is False → exit with k = 10.

Termination proof: 2**k strictly increases as k increases, and it is unbounded, so it must eventually exceed 1000. Since k rises by exactly 1 each round, the condition is guaranteed to flip to False — this is the parent's convergence guarantee (a monotonic quantity crossing a fixed boundary).

Recall Solution 5.2
n = 6
steps = 0
while n != 1:                    # stop when we hit 1
    if n % 2 == 0:
        n = n // 2
    else:
        n = 3 * n + 1
    steps += 1                   # update lives on EVERY path (no continue trap)
print(steps)                     # 8

Trace from 6: 6→3→10→5→16→8→4→2→1. That is 8 steps. Note the update steps += 1 sits after the if/else on the common path — avoiding the L3 continue pitfall. (Collatz termination for all n is famously unproven in general, but for n = 6 it clearly reaches 1.)

Recall Solution 5.3

Peel off the last digit with % 10, drop it with // 10, and build the reversed number by shifting.

n = 1234
rev = 0
while n > 0:
    last = n % 10               # last digit
    rev = rev * 10 + last       # append it to rev
    n = n // 10                 # remove last digit (this is the update)
print(rev)                      # 4321

Trace: n=1234, rev=0rev=4, n=123rev=43, n=12rev=432, n=1rev=4321, n=0. Check 0 > 0 False → exit. Prints 4321.

Termination argument (this is the Mastery skill): name the quantity that shrinks. Here n is our decreasing measure. Each round n = n // 10 divides n by 10 and floors it, so for any n > 0, the new value n // 10 is strictly smaller than the old n (e.g. 1234 // 10 = 123 < 1234; even 1 // 10 = 0). A strictly decreasing sequence of non-negative integers cannot fall forever — it must reach 0 in finitely many steps (at most the number of digits of the start value). Once n = 0, the condition n > 0 is False and the loop exits. This is a variant argument: a non-negative integer measure that strictly decreases each iteration guarantees termination — the same reasoning pattern behind Ex 5.1's monotone quantity.


Recall Master check — cover the answers
  • Ex 1.1 output? ::: 2 4 6, condition checked 4 times.
  • Ex 2.2 total? ::: 55.
  • Ex 3.2 exact output of the code as written? ::: 0 2 4 6 8 (it works because the update sits before the continue).
  • Ex 3.3 checks and final n? ::: 7 checks, ends with n = 1.
  • Ex 4.2 factorial of 5? ::: 120.
  • Ex 5.1 smallest k with 2**k > 1000? ::: 10.
  • Ex 5.2 Collatz steps from 6? ::: 8.
  • Ex 5.3 reverse of 1234? ::: 4321.

Connections

Difficulty Ladder

key skill

guarantees

L1 Recognition read a loop

L2 Application write a simple loop

L3 Analysis find the bug predict output

L4 Synthesis combine loop input break

L5 Mastery goal to terminating loop

name a shrinking quantity

termination