skip to content

Iteration and Loops

Arguing a loop correct: the invariant that holds each pass, the variant that forces it to stop, and break and continue as bounded exits. Interviewers want the argument, not a hand trace.

on this pageshow

questions

5

In a loop that processes a list of pending items, what does break do that continue does not?

level: juniorimportance: must knowfreq 70%

answer

  1. one leaves the pass, one the loop
  2. where control lands afterwards
  3. the next condition test is the target
  4. remaining items: skipped or still processed
  5. an advance inside the body gets skipped

basics

~20 s

break ends the loop itself: control resumes at the first statement after it. continue ends only the current pass, skipping the rest of the body and jumping to the loop's next condition test, so later items are still processed.

solid answer

~40 s

Both are forward jumps out of the middle of the body, but they aim at different targets. `break` abandons the loop entirely: the condition is never tested again, control resumes after the loop, and every item still pending is left unprocessed. `continue` abandons only the current pass: the rest of the body is skipped, the condition is evaluated again, and the next item is taken. Draining a list of pending dependencies, `break` is how you stop on a fatal conflict, while `continue` is how you skip a dependency that needs no work. The trap is a `continue` in a loop whose body itself performs the step to the next item — the jump skips that step, the condition sees the same state, and the loop never makes progress.

code

pseudocode · 7 lines
pseudocode
i = 0
while i < count(pending):
    item = pending[i]
    if already_installed(item):
        continue          // back to the test with i unchanged
    install(item)
    i = i + 1             // the rest of the body - skipped by continue

go deeper

for a junior

Know the one-line difference cold and say where control lands: after the loop for one, at the next condition test for the other. This is a first-screen question and a vague answer reads as never having debugged a loop.

for a middle

Explain the skipped-advance hang from the mechanism: the jump skips the rest of the body, and the step that makes progress was in the rest of the body. Then say where you would move that step.

for a senior

Show that you audit early exits in review: every value the code after the loop reads must be set on every path out, including the one the early exit takes. That is where these bugs actually ship.

for a principal

Frame it as a readability budget the team spends: each extra exit is another condition a reader must hold in mind, so a standard worth setting is that an exit must correspond to a condition someone can name in one phrase.

## Two jumps out of the middle of a body A loop body is a block that runs top to bottom and then hands control back to the loop's condition. `break` and `continue` are both **forward jumps** that leave that block early, and the whole difference between them is where control lands. - `break` leaves the **loop**. The condition is never evaluated again; execution resumes at the first statement after the loop. Whatever was still pending is left unprocessed by that loop. - `continue` leaves the **pass**. The remainder of the body is skipped and control goes straight to the loop's next condition evaluation. The loop is still alive and the next item is taken normally. Both jump forward and outward — never backward, never into the middle of some other block. That is why disciplined control flow tolerates them at all: the reader can see where each one lands without hunting for a label somewhere else in the routine, which is precisely what an unrestricted jump denies. ## Where each one lands | | break | continue | |---|---|---| | Control resumes | at the statement after the loop | at the loop's next condition test | | Condition re-evaluated | no | yes | | Items still pending | left unprocessed | processed as usual | | Natural use | a fatal conflict ends the drain | an item that needs no work this pass | | Classic failure | code after the loop reads a value this exit never set | the jump skips the step that makes progress | ## The bug continue is famous for Picture an installer walking a list of pending dependencies by index. The body reads the item at the current index, does the work, and increments the index as its last statement. Now someone adds a fast path: if the dependency is already installed, `continue`. That jump skips **the rest of the body**, and the increment is part of the rest of the body. The condition is re-tested with the index unchanged, the same already-installed item is read, the same fast path fires, and the loop spins forever. The repair is a matter of where progress lives, not of avoiding `continue`: 1. Move the step that makes progress into the loop's own header, where no `continue` can skip it. 2. Or make the body's first act consume the item — popping from a work list rather than indexing into it — so that taking the item *is* the progress. 3. Or place the skip after the progress step, so the fast path skips only the work and not the advance. The same reasoning explains why the same shape is harmless elsewhere: in a loop that pops an item off a work list at the top of the body, a `continue` further down cannot hang the loop, because the pop already happened. ## Neither jump suspends anything A common misreading is that jumping out of the body somehow puts the loop's reasoning on hold. It does not. Whatever the loop keeps true on every pass is still true at the instant either jump fires — `continue` reaches the condition with it true, exactly as a pass that ran to the end would, and `break` carries it out of the loop along with the condition that triggered it. The code after a `break` gets a different exit condition than the code after a normal exit, and it has to be written for both. That is also why the value a loop is computing must be established on **every** path out. A search loop that assigns its result only on the path that never breaks leaves the tail reading something the break path never set. ## Why the loop uses break in the first place Some loops genuinely want to test in the middle: read an item, and only after reading it can you tell whether there is anything to process. Writing that with the test at the top forces you to duplicate the read before the loop and again at the bottom of the body — the loop-and-a-half problem. A `break` in the middle removes the duplication, at the price of an exit the reader has to account for. - Reach for `break` when a condition means the loop's whole job is over or impossible. - Reach for `continue` when this one item needs nothing, and the loop's job is unchanged. - Reach for neither when the condition is really the loop's exit condition: put it in the condition, where it is visible at the top. - Never reach for either to fake a jump backwards; that is not what they do, and a loop is already the backward jump. ## What an interviewer is listening for A candidate who answers "break exits, continue skips" has the definition. A candidate who adds *where control lands* and *what the loop still owes afterwards* has the mechanism, and is the one who will spot the skipped-advance hang in review rather than in production.

  • A loop pops an item off a work list at the top of the body and uses continue further down. Why does that one not hang?
    Because the progress step already happened before the jump. The pop removed the item from the work list, so the condition is re-tested against a smaller list and the next pass takes a different item. The hang needs a skipped advance, not a continue.
  • Why does a loop whose real test sits in the middle of the body often use break?
    Because the value the test needs is only available after part of the body has run — you must read before you can tell whether there is anything to process. Writing it with a top test duplicates that read before the loop and again at the bottom; a mid-body break removes the duplication. That is the loop-and-a-half problem.

saying these in an interview costs you the question

  • Says continue exits the loop like break, just later
  • Thinks continue skips the loop's condition test as well
  • Assumes an advance written inside the body still runs on continue
  • Believes a jump out of the body suspends what the loop keeps true
  • Treats break as returning from the whole subroutine
open as a page

An installer loop pops a dependency and may push new ones onto its work list — what argues it terminates?

level: middleimportance: must knowfreq 58%

basics

~20 s

A variant: a measure that strictly decreases on every pass and cannot fall forever. The work list's length is not one here, since a pass may push. Pair the count of never-yet-visited dependencies with the list's length and compare the pairs in order.

open as a page

A work-list loop breaks as soon as it finds a conflict — what must hold at that exit point?

level: middleimportance: should knowfreq 46%

basics

~20 s

At that exit, the property the loop preserves on every pass together with the condition that fired the break must imply whatever the code after the loop assumes. The normal exit condition — the work list emptied — is false there, so the tail cannot rely on it.

open as a page

Rewriting a recursive dependency walk as a loop, what must the explicit stack hold that recursion kept implicitly?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Whatever was still to be done after each pending call came back: which item is being visited, how far through its dependencies you had got, and any partial result waiting to be combined. A call in tail position leaves nothing pending, so that rewrite needs no stack at all.

open as a page

A retry loop re-fetches an index until the fetch succeeds, with no attempt cap — can you argue it terminates?

level: seniorimportance: should knowfreq 44%

basics

~20 s

No. Nothing in the loop's own state decreases, so there is no measure to exhibit; whether it stops depends on the world outside the code. You can still show partial correctness — if it exits, the fetch succeeded — and you get termination only by introducing a bound.

open as a page