skip to content

Iteration Statements

The statements that repeat work and the traps around them: for and while, early exits, the else clause almost nobody reads correctly, and changing a container while you walk it.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

27

What is the difference between `break` and `continue` in a Python loop?

level: juniorimportance: must knowfreq 82%

answer

  1. one stops, one skips
  2. how much does each abandon?
  3. which loop do they affect?
  4. innermost enclosing loop only
  5. no break inside a comprehension

basics

~20 s

break ends the innermost enclosing loop immediately and control resumes after it. continue abandons only the rest of the current pass and moves on to the next item or condition test. Neither statement touches an outer loop.

solid answer

~40 s

`break` terminates the innermost enclosing `for` or `while` loop at once: the rest of the body, the remaining items and any further condition tests are all skipped, and execution resumes at the first statement after the loop. `continue` is much weaker — it abandons only the rest of the current pass, then the loop carries on: a `for` pulls the next item from its iterator, a `while` re-evaluates its condition. Both bind to the innermost loop that encloses them, and both are a `SyntaxError` outside any loop. Idiomatically `break` means "stop the moment I have found it" and `continue` is a filter — skip the rows I do not care about and keep scanning. Comprehensions support neither, so early exit there means a real loop, `itertools.islice`, or `next()`.

code

python · 9 lines
python
codes = [200, 500, 202, 0, 204]
kept = []
for code in codes:
    if code == 0:
        break
    if code >= 500:
        continue
    kept.append(code)
print(kept)  # [200, 202]

go deeper

for a junior

Be ready to state both in one sentence and then trace a short loop out loud, saying exactly which items still reach the body and what the loop variable holds once the loop ends.

for a middle

Explain that both bind to the innermost enclosing loop, that continue sends a while straight back to its condition test while a for pulls the next item, and that comprehensions support neither.

for a senior

In review, judge whether an early break genuinely expresses intent or hides a search that should be a next() over a generator, and whether stacked continue guards have buried the condition that actually matters.

for a principal

Own the convention: when guard-style continue is preferred over deeper nesting, and when a hand-rolled scanning loop should instead become a named, tested helper that returns its find.

Both statements alter the normal flow of a loop, but they differ in how much they abandon. ### `break` — leave the loop When `break` executes, the innermost enclosing `for` or `while` loop stops immediately. The remainder of the body is skipped, no further item is pulled from the iterator, no further condition test happens, and execution resumes at the first statement following the loop. Nothing else is unwound: the function keeps running, and the loop variable keeps whatever value it held when the `break` fired, which is exactly why the "scan until found, then use the variable" idiom works. The iterator is simply dropped, not exhausted or closed. If you were looping over a generator object you still hold a reference to, it stays suspended at the point it yielded, and a later loop over the same object resumes from there. That is a genuine gotcha when a helper hands the same iterator to two consumers. ### `continue` — leave the pass `continue` abandons only the current iteration. Everything below it in the body is skipped for that pass, and control jumps back to the top of the loop. What happens next depends on the loop kind: a `for` asks its iterator for the next item and runs the body again (or ends if the iterator is exhausted), while a `while` re-evaluates its condition expression and runs the body again only if it is still true. The most common misreading is that `continue` restarts the loop from the beginning of the sequence, or that it skips the *next* item. It does neither. It is a jump to the bottom of the current pass, nothing more. ### They bind to the innermost loop Neither statement takes an argument, and Python has no loop labels. In two nested loops, a `break` written in the inner body ends the inner loop only — the outer loop then continues with its next item, usually giving the very bug where a search "keeps running after it found the answer". Escaping several levels at once is a separate problem with its own idioms. Both statements are only legal lexically inside a loop in the same code object. Writing either at module level, or in a function body that happens to be defined inside a loop, is a `SyntaxError` at compile time rather than a runtime failure. ### Guard style A `continue` used as a guard is often the cleanest way to keep a loop body flat: filter out the passes you do not care about at the top, then write the real work at one indentation level. The alternative — wrapping the whole body in an `if` — pushes everything a level deeper and, in a long body, hides the condition far from the code it governs. Overusing it has the opposite effect: five `continue` guards in a row make the surviving cases hard to name, and that is usually a sign the filtering belongs in a comprehension or a generator that the loop then consumes. ### Comprehensions have neither `break` and `continue` are statements, and a comprehension is an expression, so `[x for x in items if break]` does not parse — it is a `SyntaxError`. A comprehension's `if` clause *filters*, which covers the `continue` case, but there is no way to stop it early. When you need early exit, the options are to write an ordinary `for` loop with `break`, to build a generator expression and take a prefix with `itertools.islice`, or to grab the first match with `next(gen, default)`. The last two keep the work lazy, so items past the stopping point are never computed at all. ### Interaction with the rest of the loop `break` and `continue` inside a `try` block are legal and cooperate with cleanup: the attached `finally` clause runs on the way out before the jump takes effect. Inside a `with` block, the context manager's `__exit__` runs likewise, so an early exit never leaks the managed resource. Neither statement is a substitute for `return`: `break` leaves the loop and keeps executing the function, while `return` leaves the function entirely. ### What an interviewer is checking At junior level this is a trace-by-hand question. Given a short loop with both statements, an interviewer wants you to say precisely which items reach the body, which value the loop variable holds afterwards, and where execution lands when the loop ends. Getting `continue`'s destination right — the *next* pass, not a restart — is the whole point.

  • Where does execution resume after a `break` in a Python `for` loop?
    At the first statement after the loop body. The remaining items are never requested, and the loop variable keeps the value it had when the `break` fired, which is what makes the scan-then-use idiom work. The iterator is dropped rather than exhausted, so a generator object you still hold a reference to stays suspended at its last `yield` and a later loop over it resumes from there.
  • How do you stop early when the code is a comprehension rather than a loop?
    A comprehension has no `break` — writing one is a `SyntaxError`, because `break` is a statement and a comprehension is an expression. Rewrite it as a `for` loop with a `break`, or keep it lazy: build a generator expression and take a prefix with `itertools.islice`, or grab the first match with `next(gen, default)`. Both stop pulling items as soon as you have enough.
  • Is `continue` allowed inside a `try` block?
    Yes. A `continue` in the `try` or `except` suite runs the attached `finally` clause on the way out and then proceeds to the next pass. `continue` inside the `finally` block itself was a `SyntaxError` before Python 3.8; it is legal now, but Python 3.14 emits a `SyntaxWarning` for it, because leaving a `finally` that way silently discards an exception still in flight.

saying these in an interview costs you the question

  • Says continue restarts the loop from the first item
  • Thinks one break leaves every enclosing loop
  • Believes continue also skips the following item
  • Confuses continue with pass, which does nothing
  • Claims break exits the function like return
  • Expects break to work inside a list comprehension

context

open as a page

What does Python's enumerate() yield, and what does its start argument change?

level: juniorimportance: must knowfreq 70%

basics

~10 s

enumerate() wraps any iterable and lazily yields (count, item) two-tuples. The start argument sets the first count, so start=1 numbers items from one. It shifts only the counter, never which items come out.

open as a page

Why does calling list.remove inside a for loop over that same list skip elements?

level: juniorimportance: must knowfreq 76%

basics

~20 s

A list iterator holds an integer index and advances it on every pass. Deleting an element shifts everything after it down one slot, so the next index lands one element too far and the shifted item is never visited.

open as a page

When does the else block of a Python for or while loop actually run?

level: juniorimportance: must knowfreq 40%

basics

~20 s

The else clause of a for or while loop runs when the loop ends normally: the iterable is exhausted or the while condition turns false. A break skips it, and an empty iterable still triggers it.

open as a page

What does Python's range(10, 0, -1) produce, and when is a range empty?

level: juniorimportance: must knowfreq 72%

basics

~20 s

range(10, 0, -1) yields 10 down to 1: start is included and stop excluded, so 0 never appears. A range is empty whenever the step cannot carry start toward stop, as in range(0, 5, -1).

open as a page

What does sys.getrecursionlimit() return on a stock CPython, and what does exceeding it raise?

level: juniorimportance: must knowfreq 72%

basics

~10 s

A stock CPython reports 1000 from sys.getrecursionlimit(). Crossing it raises RecursionError, a subclass of RuntimeError. The number counts active Python frames on the current thread, not bytes of stack.

open as a page

When does Python evaluate a while loop's condition, and what ends the loop?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Python tests a while condition once before every pass and never mid-body. The loop ends only when the body changes something that makes the condition falsy, or when break, return or an exception leaves it early.

open as a page

Python has no labelled break — how do you exit two nested loops at once?

level: middleimportance: must knowfreq 56%

basics

~20 s

break leaves only the loop it sits in. Escape both by moving the pair into a function and using return, by setting a flag the outer loop tests, or by flattening the nesting into one itertools.product loop.

open as a page

Why does Python's zip() stop at the shortest input, and how do you catch ragged data?

level: middleimportance: must knowfreq 65%

basics

~20 s

zip() ends as soon as any input is exhausted, so unequal inputs are silently truncated. Pass strict=True, added in Python 3.10, to raise ValueError on a length mismatch, or use itertools.zip_longest with fillvalue to pad instead.

open as a page

Why does inserting a key into a dict during iteration raise RuntimeError?

level: middleimportance: must knowfreq 64%

basics

~20 s

A dict iterator records the mapping's size when created and re-checks it on every step. Inserting or deleting changes that size, so the next step raises RuntimeError: dictionary changed size during iteration. Sets behave the same way.

open as a page

When is `for i in range(len(items))` justified, and what replaces it otherwise?

level: middleimportance: must knowfreq 62%

basics

~20 s

Iterate the sequence directly when you only need its elements. range(len(items)) earns its place when the index itself is the point: writing a value back into items[i], comparing neighbouring positions, or walking a sub-span of positions.

open as a page

Does a `break` inside a Python `try` block still run the `finally` clause?

level: middleimportance: should knowfreq 34%

basics

~20 s

Yes. The finally clause runs whenever control leaves the try statement, including by break, continue or return, and it runs before the jump takes effect. The try statement's own else clause, by contrast, is skipped.

open as a page

For filtering a Python list, how do you choose between a slice copy, a comprehension rebuild and a reversed del loop?

level: middleimportance: should knowfreq 48%

basics

~20 s

Rebuild with a comprehension by default: one linear pass, no mutation. Use slice assignment when other references must see the change, and a reversed index loop only when removals are rare on a large list or the work is index-based.

open as a page

How would you rewrite a Python for/else search loop without the else clause?

level: middleimportance: should knowfreq 28%

basics

~20 s

Two standard rewrites: bind a sentinel before the loop and test it afterwards, or extract the loop into a function that returns on a hit and returns a not-found value at the end. A pure search often collapses to next(generator, default).

open as a page

Does CPython perform tail-call elimination on a tail-recursive Python function?

level: middleimportance: should knowfreq 48%

basics

~10 s

No. CPython pushes a fresh frame for every call, tail position or not, so a tail-recursive function still counts against the recursion limit and still raises RecursionError. Rewrite that shape as a loop.

open as a page

Why write a Python `while True:` loop with an explicit break instead of a real condition?

level: middleimportance: should knowfreq 45%

basics

~20 s

Because the stop test needs a value the body reads. A real condition forces you to duplicate that read above the loop and again at the bottom; while True with one break keeps it in one place.

open as a page

When Python's zip() stops early over iterators, what happens to items it already pulled?

level: seniorimportance: should knowfreq 28%

basics

~20 s

They are discarded. zip pulls from its inputs left to right each round, so when a later input is exhausted mid-round, the items already taken from earlier inputs are dropped and cannot be recovered — visible whenever the inputs are iterators rather than re-iterable sequences.

open as a page

A metrics scraper prunes stale series from its dict registry inside a broad except that swallows errors, and each pass drops only one series — how do you diagnose and fix it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The loop deletes keys while iterating the registry, so the second deletion raises RuntimeError, the broad except discards it, and the pass returns having removed one series. Log the exception, then collect stale keys first and delete afterwards.

open as a page

How do you use range's step to process a 250,000-row inventory sync in fixed batches?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Step the start offsets: for i in range(0, len(rows), size), take rows[i:i + size]. Slicing clips at the end, so the final short batch is included with no clamp, and an empty input produces no batches at all.

open as a page

Is sys.setrecursionlimit a safe fix for a RecursionError raised while walking a deep chat transcript?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Only once you have proved the depth is bounded by the data rather than by a cycle. A higher ceiling lifts the Python frame count, but recursion that runs through C code still hits a separate stack guard.

open as a page

A nightly Python report job hangs in a while loop some nights — how do you diagnose it and guarantee termination?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Dump a traceback from the live process to see which loop spins, then find the body path that fails to shrink what the condition reads. Fix that path, and bound the loop with a deadline and an iteration cap.

open as a page

What does zip(*rows) return in Python, and when does that transpose idiom break?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

The star unpacks each row into a separate argument, so zip walks the rows in lockstep and yields one tuple per column — a transpose. It breaks on ragged rows, which truncate silently, and on row sources too large or too lazy to unpack.

open as a page

Why does `10_000_000 in range(0, 20_000_000, 2)` answer instantly in Python?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

A range object's membership test special-cases integers: it checks the value lies within the bounds, then that its offset from start divides evenly by the step. No values are visited, so the answer is constant time.

open as a page

Why can catching RecursionError inside a recursive walk silently truncate its output?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

The handler swallows the failure at whatever depth it struck and returns whatever was collected so far. CPython unwinds cleanly, so nothing crashes and no error surfaces — the caller simply receives a short, wrong result.

open as a page

Which Python while loops are better rewritten as for loops, and why?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Rewrite when the number of passes is dictated by an existing iterable or a repeated call rather than by state the body computes. The iterator advances for you, so there is no step to forget and no element to skip.

open as a page

Why does Python 3.14 warn about a `break` that leaves a `finally` block?

level: seniorimportance: nice to knowfreq 15%

basics

~20 s

Because leaving a finally block by break, continue or return silently discards whatever was in flight — including an exception that was propagating out of the try. Python 3.14 (PEP 765) emits a SyntaxWarning at compile time for that shape.

open as a page

A for/else scan of a shared document-conversion queue logs "nothing matched" when the list was actually empty — why, and how do you fix it?

level: seniorimportance: nice to knowfreq 15%

basics

~20 s

A loop's else means only that no break happened, which covers both "examined everything and found nothing" and "there was nothing to examine". Zero iterations is normal termination, so an empty snapshot fires the same branch. Distinguish the empty case explicitly.

open as a page