Skip to content
academia.sh

Lesson 08 / 18

Loop Control

Break, continue, the search pattern, and readability when exiting nested loops.

Contents

The previous lesson introduced loops as structures that flow from start to end: the condition is tested, the body runs, it repeats. Some problems require breaking this pattern. Once the value being searched for is found, looking at the remaining elements is unnecessary; an invalid element should be skipped without being processed.

This lesson covers the statements that redirect loop flow from the inside, and their effect on readability.

Break

The break statement ends a loop without waiting for its condition. Flow continues immediately below the loop.

measurements = [12, 18, 7, 25, 14]
LIMIT = 20

for measurement in measurements:
    if measurement > LIMIT:
        print("first measurement exceeding the limit:", measurement)   # 25
        break

The justification for break is not only speed. Written without break, this loop would find the last measurement exceeding the limit; when what is sought is the first one, exiting is part of the meaning.

Infinite loops also get their exit from here: the condition is written as always true, and the decision to exit is made at some point inside the body. This form is preferred when the exit condition is computed partway through the iteration.

Continue

The second statement ends the current iteration early and moves to the next one. The loop does not terminate.

raw = [12, -1, 18, -1, 25]

total = 0
counted = 0
for measurement in raw:
    if measurement < 0:      # marker for an invalid measurement
        continue
    total += measurement
    counted += 1

print(total, counted)        # 55 3

The same work can also be written by wrapping the body in a condition block. The choice between the two forms is made by looking at indentation depth: the continue statement rules out invalid cases first and keeps the rest of the body flat. If the body is long, this is a clear gain.

The Search Pattern and the Not-Found Case

The most common use of break is search. Search’s hidden question is always the same: what happens if what is sought is not found?

def find_first_exceeding(measurements: list[int], limit: int) -> int | None:
    """Returns the first measurement exceeding the limit; returns None if there is none."""
    for measurement in measurements:
        if measurement > limit:
            return measurement
    return None               # loop ended without exiting: not found

print(find_first_exceeding([12, 18, 7, 25, 14], 20))    # 25
print(find_first_exceeding([12, 18, 7], 20))            # None

The return None on the last line handles the case where the loop is exhausted. If this line were forgotten, the function would silently return an empty value, and the caller would encounter an unexpected value instead of understanding that the search failed.

There are three ways to report a not-found case, and the choice depends on what the caller will do: return a special value, raise an error, or return a “was it found” flag together with the result. The first option is the most common; but the returned value must be checked, otherwise the empty-value problem defined in the basic data types lesson arises.

Some languages offer a separate block that runs when the loop ends without ever exiting early; it makes it possible to write the same work without a flag variable. This construct is not universal.

The Flag Variable

When break is unavailable or not suitable, carrying the result in a logical variable is a common pattern:

found = False
for measurement in [12, 18, 7]:
    if measurement > 20:
        found = True
        break

print(found)                 # False

The flag reports, once the loop ends, which way it was exited. The pattern is correct, but its cost is readability: the reader has to track where the flag is set and where it is read. If the search can be pulled out into a function, returning directly is usually clearer.

Breaking Out of Nested Loops

A break statement ends only its own loop. In a two-level search, exiting the inner loop does not stop the outer one.

matrix = [[1, 2, 3],
          [4, 5, 6],
          [7, 8, 9]]

def find_position(matrix: list[list[int]], target: int) -> tuple[int, int] | None:
    """Returns the target's (row, column) position; None if there is none."""
    for i, row in enumerate(matrix):
        for j, value in enumerate(row):
            if value == target:
                return (i, j)       # exits both loops at once
    return None

print(find_position(matrix, 6))     # (1, 2)
print(find_position(matrix, 99))    # None

There are three options. Pulling the code into a function and using return, as above, is the most readable: a single statement exits every level. Using a flag variable requires a separate exit condition at every level and quickly becomes unreadable as depth grows. Some languages offer a labeled break; which loop to exit is written explicitly.

This is the first example of functions being used not only for reuse but also to simplify flow. The next topic covers this tool in detail.

Modifying the Loop Variable

One warning: modifying a counted loop’s variable inside the body does not have the expected effect in many languages, or it breaks the termination guarantee. In the same way, modifying a collection while it is being iterated over — adding or removing an element — leads to undefined or surprising behavior.

The safe pattern is to collect changes in a separate collection and apply them after the loop ends:

measurements = [12, -1, 18, -1, 25]

valid = [measurement for measurement in measurements if measurement >= 0]   # a new list is produced
print(valid)                 # [12, 18, 25]
print(measurements)          # [12, -1, 18, -1, 25]  — the original list is unchanged

The notation in the last example is a shorthand form of a loop, and it has a counterpart in many languages. It reduces filtering and transformation operations to a single line; when the condition grows complex, an explicit loop stays more readable.

Break and Lazy Iteration

The gain from break is proportional to the number of elements not processed. If the collection is already sitting in memory, the gain is only time. But if elements are produced on demand, break also eliminates the cost of the elements never produced.

This mode of production is called lazy iteration: values are not precomputed and collected in a list; they are computed on demand, one iteration at a time.

def measurement_source():
    """Produces values on demand; not all of them are held in memory at once."""
    for base in range(1, 1_000_000):
        yield base * 3

for value in measurement_source():
    if value > 20:
        print("first exceeding:", value)   # first exceeding: 21
        break

The loop exits on the seventh iteration; the remaining values are never computed. The same work could also be done by first building a list of a million elements and then searching it — the result is the same, the cost is not comparable.

The distinction is decisive for large data sources: the difference between reading an entire file into memory and processing it line by line is an application of the same idea.

Summary

  • Break ends a loop without waiting for its condition; it is part of the meaning in problems that search for the first match.
  • The continue statement ends only the current iteration, and ruling out invalid cases first reduces indentation depth.
  • In the search pattern, the not-found case is handled explicitly; the special value returned must be checked by the caller.
  • A flag variable is an alternative to break, but it adds tracking overhead for the reader.
  • Break ends only its own loop; in nested loops, the most readable solution is to pull the search out into a function and use return.
  • Modifying a collection while it is being iterated over is not safe.

Next Step

Up to this point, control structures were written directly as code. For a complex problem, the solution itself is designed first, then translated into code. The next lesson covers writing a solution independent of any language — pseudocode — and how a solution is tested before it is turned into code.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close