---
title: 'Loop Control'
source: 'https://academia.sh/en/courses/programming-fundamentals/loop-control'
course: 'Programming Fundamentals'
language: en
updated: '2026-08-17T18:08:23+00:00'
license: 'CC BY-SA 4.0'
---

# Loop Control

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

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.

```python
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.

```python
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?**

```python
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:

```python
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.

```python
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:

```python
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.

```python
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.
