PYTHON / LOOPS
while loops and termination conditions
Write while loops whose condition is tested before each pass, and reason about what in the body actually drives them to stop.
What you will learn
- Place loop state before the loop and update it on every path through the body
- Express the condition as a quantity that strictly moves toward becoming false
- Order compound conditions so the bounds check short-circuits before the lookup
- Prefer >, < over != when the state can step past the target value
Understanding while loops and termination conditions
A while loop evaluates its condition, and only if the result is truthy does it run the body; then it goes back and evaluates the condition again. Nothing checks the condition in the middle of the body, so once a pass starts it finishes, even if the state changes on the first line. That also means a while loop can run zero times: if the condition is false when execution first reaches it, the body is skipped entirely.
The condition is just an expression re-evaluated in the current scope, so it can only change if the body changes something the expression reads. This is why a while loop needs three cooperating pieces: state initialised before the loop, a condition written in terms of that state, and a body that moves the state. Forget any one of them and the loop either never starts or never ends.
To convince yourself a loop terminates, find a quantity that strictly decreases on every pass and is bounded below by the point where the condition turns false. In n //= 2 with the condition n > 1, n shrinks by at least one each pass and cannot go below 1, so the loop must stop. When no such quantity exists, for example when a value converges by floating point arithmetic or comes from a network, add an independent counter and let it participate in the condition so there is a hard ceiling on passes.
n = 1000
steps = 0
while n > 1:
print(f"n = {n}")
n //= 2
steps += 1
print(f"stopped at n = {n} after {steps} halvings")A while loop stops only because the body changes something its condition reads, and that change must strictly move the condition toward false.
Worked examples
A loop that provably ends
Euclid's algorithm terminates because the remainder is strictly smaller than the divisor on every pass.
a, b = 252, 105
while b != 0:
a, b = b, a % b
print(a, b)
print("gcd:", a)Example explained
Line 1b != 0 reads b, and the body reassigns b, so the condition can actually change.
Line 2a % b is always in the range 0 to b - 1, so the new b is strictly smaller than the old one.
Line 3A non-negative integer cannot shrink forever, which is the termination argument for this loop.
Line 4When b reaches 0 the condition fails and a holds the last non-zero divisor, the gcd.
Two conditions and short-circuit order
Combining a bounds check with a sentinel check, where the order of the operands prevents an IndexError.
queue = [3, 0, 7, -1, 9]
i = 0
while i < len(queue) and queue[i] != -1:
print("processing", queue[i])
i += 1
print("stopped at index", i)Example explained
Line 1i < len(queue) comes first, so if i runs off the end, and short-circuits and queue[i] is never evaluated.
Line 2queue[i] != -1 is an explicit comparison, not a truthiness test, which is why the 0 at index 1 is still processed.
Line 3i += 1 is the only thing that moves the first half of the condition toward false.
Line 4The loop stops at index 3 without consuming the sentinel, so i tells you where it halted.
Stepping past the stop value
Why a decreasing counter should be compared with an inequality rather than equality.
count = 3
while count > 0:
print("tick", count)
count -= 2
print("final", count)Example explained
Line 1The condition is only checked between passes, so the second pass runs fully and leaves count at -1.
Line 2count > 0 is false for -1, so the loop exits even though count never equalled 0.
Line 3Written as while count != 0 this loop would never terminate, because the step of 2 jumps over 0.
Line 4final -1 shows the state after the loop still reflects the overshoot.
Important notes
A while loop whose condition is false at first contact runs zero times, so any variable you expect the body to set must already have a value afterwards.
The condition uses ordinary truthiness, so while items: loops until the list is empty, but that also means a value of 0 or an empty string ends the loop even if you meant only None to stop it.
Common mistakes
Updating a different variable than the condition reads, for example testing while n > 1 while decrementing count: the condition never changes and the program hangs until you interrupt it with Ctrl+C.
Putting the increment inside an if inside the body, so on the passes where the branch is skipped the state stands still and the loop spins forever on that value.
Reading input or an item before checking the bound, as in while queue[i] != -1 and i < len(queue), which raises IndexError instead of stopping because the failing check is evaluated second.
Try it yourself
Change, predict, then run
Start with total = 1 and steps = 0, then write a while loop that doubles total until it is at least 1000, counting the passes. Print total and steps after the loop and check that total is 1024 and steps is 10.
Open the Python workspaceCheck your understanding
count starts at 5 and the body ends with count -= 2. Which condition makes the loop run forever?
- while count != 0:
- while count > 0:
- while count >= 1:
- while count > -100:
Show answer
From 5 the values are 5, 3, 1, -1, -3 and so on, so count is never exactly 0 and count != 0 stays true forever. while count > -100 is tempting as the runaway answer, but count keeps decreasing and eventually drops below -100, so that loop does terminate; it just takes many more passes.