Python Part I · Module 10 · Loops
The else Clause on a Loop
Chapter 10.7 left a loose end. A loop that may stop early has two ways to end — it found what it was looking for, or it ran out of items — and afterwards the code has no way to tell which happened. The fix used there was a flag: declare it before the loop, set it at the break, check it after. Three lines of bookkeeping for something the loop already knows. Python has a construct that removes them, and it is the single strangest piece of syntax in the language: a loop can carry an else. Almost everyone's first reading of it is wrong, because the keyword is a poor name for what it does, so this chapter settles what it pairs with before showing what it is for.
Intermediate14 min readPythonPart IFundamentalsLoopsControl Flow
Part I · Module 10 · Chapter 10.8 · The else Clause on a Loop
1. The Flag This Replaces
From the end of Chapter 10.7 — a search that must report not-found:
lines = ["INFO start", "INFO end"]
found = False
for line in lines:
if "PASS" in line:
print("found:", line)
found = True
break
if not found:
print("no PASS marker found")no PASS marker foundCorrect, and the loop's actual work is two lines of the seven. The rest exists only to carry one bit of information — did we break? — past the end of the loop.
2. The Loop else
lines = ["INFO start", "INFO end"]
for line in lines:
if "PASS" in line:
print("found:", line)
break
else:
print("no PASS marker found")no PASS marker foundSame output. The flag is gone, along with the declaration and the check.
And when the search succeeds, the else does not run:
lines = ["INFO start", "PASS smoke", "INFO end"]
for line in lines:
if "PASS" in line:
print("found:", line)
break
else:
print("no PASS marker found")found: PASS smoke3. The Rule
The else block runs when the loop finished without executing a break.
That is the entire definition, and three things follow from it that are not obvious:
| Situation | Does else run? | Why |
|---|---|---|
the loop hit a break | no | a break happened |
| the loop ran out of items | yes | no break happened |
| the collection was empty | yes | zero passes, so no break happened |
a continue was used | yes | continue is not break |
the loop contains no break at all | yes, always | there was never a break to happen |
Note the indentation: else aligns with for, not with the if inside the body. That alignment is what tells Python — and the reader — which statement it belongs to. Chapter 9.2's rule, applied to a different pair.
4. The Keyword Is a Bad Name
Here is the misreading everyone starts with: that the else pairs with the if inside the loop, and runs on passes where the condition was false. It does not. It is not inside the loop at all; it runs at most once, after the loop is over.
The second misreading is that else means otherwise in the ordinary sense. It does not mean that either.
Read it as nobreak:. That is what it means, and that word was seriously proposed when the feature was designed. Substituting it mentally makes every example in this chapter read correctly on the first pass:
for line in lines:
...
break
nobreak: # <- how to read `else:` here
print("not found")5. continue Does Not Suppress It
This is the case that catches people who have understood everything else:
for n in [1, 2, 3]:
if n == 2:
continue
print("n =", n)
else:
print("else still ran — continue is not break")n = 1
n = 3
else still ran — continue is not breakcontinue skipped a pass; it did not leave the loop. The rule is about break specifically, and nothing else in the language suppresses the else.
6. An Empty Collection Still Runs It
Zero passes means zero opportunities to break, so the else runs:
for line in []:
if True:
break
else:
print("else ran: zero passes, so no break happened")else ran: zero passes, so no break happenedThis is usually what you want — searching an empty log should report not found — but it is worth knowing it is the behaviour, because "we never looked" and "we looked and found nothing" arrive at the same branch.
7. An else With No break Is Pointless
If the loop contains no break anywhere, the else always runs:
for n in [1, 2]:
print(n)
else:
print("else always runs when the loop contains no break")1
2
else always runs when the loop contains no breakThat is identical to writing the line after the loop with no else at all — and worse, because a reader has to check the whole body for a break before concluding it is unconditional.
8. It Works on while Too
Same rule — the else runs when the loop ended by its condition going false rather than by a break:
attempts = 0
while attempts < 3:
attempts += 1
print("attempt", attempts)
else:
print("while-else: ran out of attempts without breaking")attempt 1
attempt 2
attempt 3
while-else: ran out of attempts without breakingattempts = 0
while attempts < 3:
attempts += 1
if attempts == 2:
print("breaking at", attempts)
break
else:
print("this does not print")
print("after")breaking at 2
afterFor a bounded retry this is exactly the shape Chapter 10.5 wanted: the break is the success exit, and the else is the bound was reached and it never worked — stated once, in the right place, with no flag.
attempt_results = ["timeout", "timeout", "timeout"]
for attempt in range(3):
result = attempt_results[attempt]
print("attempt", attempt + 1, "->", result)
if result == "ok":
print("succeeded on attempt", attempt + 1)
break
else:
print("gave up after 3 attempts")attempt 1 -> timeout
attempt 2 -> timeout
attempt 3 -> timeout
gave up after 3 attemptsNote what changed from Chapter 10.5: the retry is now a for over range(3) rather than a while with a counter, because break supplies the early exit that was missing in Chapter 10.6 Section 4. The bound is the range; the success exit is the break; the give-up message is the else.
9. Should You Use It?
An honest verdict, because this is a construct on which competent engineers disagree.
In favour: it removes a flag, it puts the not-found message adjacent to the search rather than three lines below it, and it is impossible to get the flag logic wrong because there is no flag.
Against: the keyword says the opposite of what it does, every reader who has not met it will misread it once, and some teams ban it outright in review.
The practical position. Use it where the pattern is unmistakable — a short search loop whose break is visible in the same screenful. Avoid it in a long body where the break is far from the else, because then the reader has to search for the thing the clause depends on. And know it regardless, because you will read it.
The flag version is never wrong:
lines = ["INFO start", "INFO end"]
found = False
for line in lines:
if "PASS" in line:
found = True
break
if found:
print("found a PASS marker")
else:
print("no PASS marker found")no PASS marker foundIt is longer, and it is immediately readable by everyone. That is a real argument.
10. Predict, Run, Observe
# A
for n in [1, 2, 3]:
if n == 5:
break
else:
print("A: else ran")
# B
for n in [1, 2, 3]:
if n == 2:
break
else:
print("B: else ran")
print("B: after")
# C
for n in []:
print("C: body")
else:
print("C: else ran")
# D
found = ""
for name in ["clk", "rst", "data"]:
if name.startswith("r"):
found = name
break
else:
found = "none"
print("D:", found)The verified output:
A: else ran
B: after
C: else ran
D: rstA — nothing equals 5, so no break, so the else runs.
B — the break at 2 suppresses the else; only the line after the loop prints.
C — zero passes, no break, else runs. The body never did.
D — "rst" matches, so the loop breaks and found keeps the name. Had no name started with r, the else would have set it to "none".
11. Exercises
Exercise 1 — delete the flag. Rewrite Section 1's program using a loop else and confirm the output is unchanged. Count the lines removed.
Exercise 2 — predict the four cases. For a loop with a break inside, say whether the else runs when: (a) the break is reached (b) the collection is exhausted (c) the collection is empty (d) a continue ran on every pass. Verify all four.
Exercise 3 — the misreading. Write down, in one sentence, what you would have guessed else meant before reading Section 4. Then write the correct one-sentence rule.
Exercise 4 — find the first gap. Given seeds = [4210, 4211, 4213, 4214], find the first position where the seed is not one more than the previous, and report it. If the seeds are consecutive throughout, report that instead — using a loop else.
Exercise 5 — the pointless else. Write a loop with an else and no break. Run it, then delete the else and unindent its body. Explain in one sentence why the two programs are the same and which is better.
Exercise 6 — retry with for. Rewrite the bounded retry from Chapter 10.5 as a for over range(4) with a break and an else, using attempt_results = ["timeout", "ok", "timeout", "timeout"]. Print which attempt succeeded.
Exercise 7 — while-else. Write a while loop that drains a queue of three jobs, with an else that prints queue emptied cleanly. Then add a break that triggers on a job named "bad" and verify the else is skipped.
Exercise 8 — argue both sides. In two sentences each, make the case for the loop else and the case for the flag. Then say which you would use in a 40-line loop body and why.
12. Summary
- A loop may carry an
else, and it runs when the loop finished without executing abreak. - It pairs with the loop, not with the
ifinside it — the alignment withfororwhileis what says so. - Read it as
nobreak:. The keyword is widely agreed to be a poor name, and substituting the better one makes every example read correctly. - A
continuedoes not suppress it. The rule is aboutbreakonly. - An empty collection still runs it — zero passes means no break happened.
- An
elseon a loop with nobreakalways runs, which makes it identical to code after the loop and actively misleading. Delete it. - It works on
whileas well asfor, where it means the condition went false rather than the loop breaking out. - A bounded retry is its best use:
range()is the bound,breakis the success exit,elseis gave up. - The flag version is never wrong, and is more widely readable. Use the
elsewhere thebreakis visible nearby.
The next chapter removes two more pieces of manual bookkeeping from loops.
Where this fits
Part of the Python curriculum.
