Loops & Iteration

6 min read

Reading Progress0%
CS Fundamentals Index
Tier 1 -- Foundations
Tier 2 -- Core Concepts
Tier 3 -- Debugging & Tradeoffs
CS Fundamentals Index
Tier 1 -- Foundations
Tier 2 -- Core Concepts
Tier 3 -- Debugging & Tradeoffs

Loops & Iteration

1. What Is It?

A loop is a construct that executes a block of code repeatedly, usually until a condition becomes false or until there are no more items to process. Without loops you'd have to unroll every repetition by hand — impossible for anything but tiny, fixed-size tasks.

The real idea behind loops isn't the for keyword — it's iteration: a disciplined walk through a sequence of values, with a well-defined start, end, and per-step behavior. Every off-by-one bug, infinite loop, and "why is this so slow?" question comes from misunderstanding one of those three.

QUICK CHECK

A backend developer needs to send a welcome email to every user in a database table that currently has 50,000 rows, and the count grows daily. Which approach best reflects why loops exist as a programming construct?

Choose one answer

2. How It Works

There are three fundamental loop shapes, and all languages provide some mix of them:

  1. Counted loop — iterate a known number of times (for i in range(n)).
  2. Condition loop — iterate while a condition holds (while cond:).
  3. Iterator loop — walk every element of a collection (for x in xs:).
# Python
# counted
for i in range(5):
    print(i)                    # 0..4

# condition
n = 100
while n > 1:
    n //= 2                     # 100, 50, 25, 12, 6, 3, 1 — exits when n == 1

# iterator
for name in ["ada", "lin", "hopper"]:
    print(name)
// Java
for (int i = 0; i < 5; i++) {              // counted
    System.out.println(i);
}

while (n > 1) { n /= 2; }                  // condition

for (String name : names) {                // iterator (for-each)
    System.out.println(name);
}
// C++
for (int i = 0; i < 5; ++i) { ... }        // counted
while (n > 1) { n /= 2; }                   // condition
for (const auto& name : names) { ... }      // iterator (range-based)

Under the hood: iterators

The range-based / for-each loop is sugar for an iterator protocol. The language asks the collection for an iterator; at each step it asks "next element, please" until the iterator signals it's done.

# Python's for loop is equivalent to:
it = iter(xs)
while True:
    try:
        x = next(it)
    except StopIteration:
        break
    # ... use x

Java's for-each compiles to calls on Iterator.hasNext() / Iterator.next(). C++ range-for expands to a loop driven by begin() / end() iterators.

This matters because you can make your own types iterable (implement the protocol) and they slot into for seamlessly. It's also why modifying a collection mid-iteration often crashes: the iterator's invariants are broken.

break, continue, return

  • break — exit the innermost loop immediately.
  • continue — skip the rest of this iteration, proceed to the next.
  • return (inside a function) — exits the loop and the function at once.

Python adds for ... else: — the else runs only if the loop finished without break. Unusual but useful for search loops.

QUICK CHECK

A backend developer writes a for-each loop that iterates over a live database result set, and inside the loop body deletes rows that match a certain condition. After deploying, the service intermittently crashes with a 'ConcurrentModificationException' (Java) or similar error. What is the most likely root cause?

Choose one answer

3. What You Actually Need to Know

  • Off-by-one errors come from fuzzy bounds. Standard convention is half-open: [start, end) — inclusive start, exclusive end. That's why range(n) gives 0..n-1 and for (i = 0; i < n; i++) is the canonical C-style loop.
  • Infinite loops happen when the condition never changes. while (x > 0) inside a body that never decrements x will hang forever. If a loop freezes your program, add a print of the controlling .
  • Modifying a collection while iterating is usually broken. Java throws ConcurrentModificationException. Python may skip elements or raise RuntimeError: dictionary changed size during iteration. C++ iterators become invalidated — undefined behavior. Build a new list instead, or iterate over a copy.
  • Use the right shape. Don't fake a for-each with an index loop (for i in range(len(xs)): x = xs[i]) — it's slower, uglier, and bug-prone. Use for x in xs:. If you also need the index, use enumerate(xs) (Python) or for (int i = 0; i < xs.size(); i++) with intent.
  • Prefer iterator protocols / generators for large data. Loading a 10GB file and iterating a list of all its lines will run out of memory; iterating a file handle reads line by line. Python generators (yield), Java Stream, C++ ranges all express "lazy" iteration.
  • Debugging clues:
    • Loop runs one too few / one too many times → off-by-one; check < vs. <=.
    • Loop never terminates → update step missing, or condition can never become false.
    • ConcurrentModificationException / RuntimeError: dictionary changed size → you mutated a collection mid-iteration.
    • Wrong value for i after a C-style for → loop body rebound i; don't.
QUICK CHECK

A backend service processes a list of active user sessions and removes expired ones during iteration. In Python, the code does for session in sessions: if session.expired(): sessions.remove(session). What is the most likely problem with this approach?

Choose one answer

4. Language Differences

AspectPythonJavaC++
C-style for(init; cond; step)No — use range or whileYesYes
For-eachfor x in xs:for (T x : xs)for (auto& x : xs)
do-while (run body at least once)No (emulate with while True: ... if not cond: break)Yes (do { ... } while (cond);)Yes
Generators / lazy iterationyieldIterator, StreamIterators, ranges (C++20)
for ... else (runs if no break)YesNoNo
Unsigned underflow hazardN/A (int is arbitrary precision)N/A (ints are signed)Yes — size_t i = ... ; for (; i >= 0; --i) loops forever because size_t is unsigned

The C++ unsigned-loop trap is worth memorizing. for (size_t i = v.size() - 1; i >= 0; --i) is an infinite loop — i wraps around to a huge positive number instead of going negative.

QUICK CHECK

A C++ developer writes the following loop to iterate backward over a vector and remove flagged elements:

for (size_t i = v.size() - 1; i >= 0; --i) {
    if (shouldRemove(v[i])) v.erase(v.begin() + i);
}
During testing, the program hangs in an infinite loop. What is the most likely cause?

Choose one answer

5. Tradeoffs & Decisions

  • Index-based vs. for-each. Use for-each by default — clearer, harder to get wrong, usually the same performance. Use index-based only when you need the index for logic (pairing, distance, reverse traversal).
  • Eager vs. lazy iteration. Building a full list ([f(x) for x in data]) is fine for small data and easy to reason about. For pipelines over big data, lazy iteration (generators, streams) avoids peak memory and enables early termination.
  • vs. iteration. can express tree/graph traversal more naturally. Iteration avoids overflow and is usually faster. Python has no tail-call optimization; a deep recursive loop will blow the .
  • Parallel iteration. Java streams (.parallelStream()), C++17 parallel algorithms, and explicit thread pools let you split iterations across cores. Worth it only when each iteration is substantial work and the data can be partitioned cleanly.

If you see a loop mutating a shared collection it's iterating over, it almost always indicates a bug or an architectural issue — build a new collection instead. You'd choose a lazy generator when memory matters more than simplicity.

QUICK CHECK

Your backend service needs to process a log file containing hundreds of millions of records to compute aggregated metrics. The processing pipeline reads each record, applies a transformation, and filters out irrelevant entries before aggregation. Which iteration strategy is most appropriate here, and why?

Choose one answer

6. Interview Cheat Sheet

  • The three loop shapes are counted (known count), condition (until a predicate changes), and iterator (walk a collection).
  • For-each loops are sugar over an iterator protocol — the loop asks the collection for elements until it says "done."
  • Off-by-one errors come from confusing inclusive vs. exclusive bounds; the half-open [start, end) convention is standard.
  • Never modify a collection while iterating it. Build a new one or take a snapshot.
  • break exits the innermost loop; continue skips to the next iteration.
  • Python generators / Java streams / C++ ranges enable lazy iteration — process one element at a time without materializing the full sequence.

Follow-ups:

  • "What does for x in xs actually do?" — Calls iter(xs) once, then next() repeatedly until StopIteration.
  • "Why might iterating over a dict and mutating it crash?" — The iterator holds state referring to the dict's internal structure; mutating it breaks that state.
  • "When should you use while over for?" — When you don't know the iteration count in advance and the stopping condition depends on computed state (e.g., convergence loops).
Glossary History

Click dotted jargon to save explanations here.

Glossary History

Click dotted jargon to save explanations here.