Python Part I · Module 10 · Loops
Choosing Between for and while
You now have both loop statements, and in a strict sense either can do the other's job — any for loop can be rewritten as a while, and many while loops can be rewritten as a for. That makes the choice a judgement rather than a rule, which is why it is worth a chapter. The usual advice is to use a for loop when you know how many passes there will be, and that advice is wrong often enough to mislead: range() turns any known count into a for loop, so knowing the count never rules one out. This chapter gives the question that actually discriminates, then converts a loop each way so the cost of answering it wrongly is something you can see rather than something you are told.
Foundation14 min readPythonPart IFundamentalsLoopsDesign
Part I · Module 10 · Chapter 10.6 · Choosing Between for and while
1. The Two Statements in One Line Each
for runs its body once for each item in a collection. The collection decides when it ends.
while runs its body as long as a condition is true. The body decides when it ends, by changing what the condition depends on.
That is the whole difference, and every point below follows from it.
2. The Deciding Question
Ask: is there a collection?
Not do I know how many passes — that question sounds right and fails immediately, because range(100) turns any known count into a for loop. Ten seeds, a hundred retries, three repeats: all collections, all for.
Ask instead whether the passes correspond to things:
- Forty tests → a collection.
for. - Two million log lines → a collection.
for. - Seeds 4210 to 4219 →
range()makes a collection.for. - Checking a job until it finishes → not a collection. The checks are produced by the situation, and there is no list of them.
while. - Retrying until it works or you give up → not a collection.
while.
When the answer is yes, the collection already contains the information that ends the loop, and a for uses it. When the answer is no, nothing in your program knows when to stop, so you must write the condition yourself.
3. A for Loop Written as a while
Both of these print the same three lines:
tests = ["smoke", "reset", "burst"]
for test in tests:
print(test)smoke
reset
bursttests = ["smoke", "reset", "burst"]
i = 0
while i < len(tests):
test = tests[i]
print(test)
i += 1smoke
reset
burstThe second version introduced three things the first did not have: a counter, a bound to compare it against, and an increment. Each is a place to be wrong, and the failures are not subtle.
Forget the increment and it never ends. Compare against the wrong bound and it reads past the end:
tests = ["smoke", "reset", "burst"]
i = 0
while i <= len(tests):
print(tests[i])
i += 1IndexError: list index out of range<= instead of < — one character — and the loop asks for tests[3], which does not exist. The for version cannot make this mistake, because it never names a position at all.
4. A while Loop Written as a for
The conversion goes the other way only when the passes really are a collection in disguise.
This while is a counted repeat wearing a disguise:
attempts = 0
while attempts < 3:
attempts += 1
print("attempt", attempts)attempt 1
attempt 2
attempt 3Three passes, known in advance, counter maintained by hand. range() already is that collection:
for attempt in range(1, 4):
print("attempt", attempt)attempt 1
attempt 2
attempt 3The counter, the comparison and the increment all disappear, because range supplies them.
Now the retry from Chapter 10.5, which looks similar and is not:
attempt_results = ["timeout", "timeout", "ok"]
attempts = 0
ok = False
while attempts < 3 and not ok:
result = attempt_results[attempts]
attempts += 1
print("attempt", attempts, "->", result)
if result == "ok":
ok = True
print("ok =", ok, "after", attempts, "attempts")attempt 1 -> timeout
attempt 2 -> timeout
attempt 3 -> ok
ok = True after 3 attemptsThis one has an upper bound of three, so there is a collection — range(3) — but the loop also has to stop early when it succeeds, and a for loop has no way to express that with what you have so far. The statement that gives it one is break, which is the next chapter. Until then, the two-part while condition is the honest way to write it.
5. The Decision Table
| The problem | Collection? | Statement | Why |
|---|---|---|---|
| every test in a suite | yes | for | the list ends the loop |
| every line of a log | yes | for | the lines end the loop |
| seeds 4210–4219 | yes, via range() | for | the range ends the loop |
| repeat exactly five times | yes, via range() | for | no counter to maintain |
| every character of a name | yes | for | a string is a collection |
| every key of a dictionary | yes | for | a dictionary is a collection |
| wait until a job finishes | no | while | the queue decides, not you |
| retry until it works, up to 3 times | no | while | stop early on success |
| drain a queue until it is empty | no | while | the body shrinks the queue |
| keep halving until below a limit | no | while | the value decides |
The pattern in the right column: a for loop is right when something outside the body already knows how many passes there are.
6. Draining: The Clearest while
One case deserves its own example, because reaching for a for loop here is actively wrong:
queue = ["j1", "j2", "j3"]
while len(queue) > 0:
job = queue.pop()
print("processing", job, "- remaining:", len(queue))
print("queue empty:", queue)processing j3 - remaining: 2
processing j2 - remaining: 1
processing j1 - remaining: 0
queue empty: []The body removes items from the very collection the loop depends on. A for loop over queue would be iterating a list while it shrinks, which Chapter 10.12 shows produces silently skipped items. A while over its length is correct precisely because it re-checks the condition each pass, after the body has changed it.
Note also the shorter spelling available here — while queue: — since an empty list is falsy, which Chapter 9.5 established:
queue = ["j1", "j2"]
while queue:
print("processing", queue.pop())
print("done")processing j2
processing j1
done7. The Third Category
The two-way split is not complete. A great deal of real code walks a collection — so it is a for loop — but needs to stop before reaching the end, or skip some items:
- scan a log and stop at the first fatal
- go through a suite, skipping tests marked as known failures
- search a list and report whether anything matched
These are for loops with an exit, not while loops. Trying to force them into a while reintroduces the counter from Section 3 for no benefit. The right tools are break and continue, which is Chapter 10.7, and the loop-else that reports which exit was taken, which is Chapter 10.8.
8. Predict, Run, Observe
For each, say which statement is right and what it prints.
# A
total = 0
for n in [4, 5, 6]:
total += n
print("A:", total)
# B
value = 100
steps = 0
while value > 10:
value = value // 2
steps += 1
print("B:", value, "after", steps, "steps")
# C
names = ["clk", "rst"]
i = 0
while i < len(names):
print("C:", names[i])
i += 1
# D
stack = [1, 2, 3]
while stack:
print("D:", stack.pop())The verified output:
A: 15
B: 6 after 4 steps
C: clk
C: rst
D: 3
D: 2
D: 1A — a collection, so for. Correct as written.
B — no collection: how many halvings 100 needs is not known without doing them. while is right. 100 → 50 → 25 → 12 → 6.
C — a collection walked by index with a while. It works and it is the wrong statement; for name in names: is Section 3's point.
D — the body empties the list, so while is right. .pop() takes from the end, hence 3, 2, 1.
9. Exercises
Exercise 1 — classify. For each, say for or while and give the one-sentence reason: (a) print every signal name in a list (b) keep doubling a buffer size until it exceeds 4096 (c) run a test at each of 20 seeds (d) poll a licence server until one is free (e) count vowels in a signal name (f) remove items from a queue until it is empty.
Exercise 2 — convert and compare. Rewrite for ch in "axi": print(ch) as a while loop. Count the lines of each and name the three things the while version added.
Exercise 3 — the off-by-one. Write Section 3's while loop with <= instead of < and record the exact error. Then explain in one sentence why the for version cannot produce it.
Exercise 4 — remove the counter. Rewrite each as a for over a range: (a) n = 0 / while n < 5: / print(n) / n += 1 (b) a loop that prints 10, 8, 6, 4, 2.
Exercise 5 — the one that will not convert. Explain in three sentences why the retry in Section 4 cannot become a for loop with what you know so far, and name the two statements from later chapters that would let it.
Exercise 6 — drain safely. Write a loop that processes jobs = ["a", "b", "c"] by removing each from the front with .pop(0) until empty, printing the remaining count each pass. Then say what would go wrong with for job in jobs:.
Exercise 7 — truthiness. Rewrite while len(queue) > 0: in the shorter form and say which chapter's rule makes it equivalent. Then give one case where the longer form would be clearer.
Exercise 8 — write the deciding question. In two sentences and without naming either statement, write the test you would apply to a new problem to decide which loop it wants. Apply it to: "report the first log line containing UVM_FATAL".
10. Summary
forruns once per item in a collection; the collection ends it.whileruns while a condition holds; the body ends it.- The deciding question is "is there a collection?" — not "do I know the count", which
range()makes irrelevant. - Any
forcan be written as awhile, at the cost of a counter, a bound and an increment — three places to be wrong, including an off-by-one that raisesIndexError. - A
forloop's real advantage is what it cannot express: no index, so no index bug. - A
whileconverts to aforonly when the passes are genuinely a collection. A safety bound is not a collection. - Draining a collection wants a
while, because the body changes the thing the condition tests. while queue:is the idiomatic empty check, since an empty collection is falsy.- A third category exists: a
forloop over a collection that must stop early or skip items. That wantsbreakandcontinue, not awhile.
The next chapter adds the two statements that give a for loop an exit.
Where this fits
Part of the Python curriculum.
