6 min read
CS Fundamentals Index
Tier 1 -- Foundations
Object-Oriented Programming
Tier 2 -- Core Concepts
Tier 3 -- Debugging & Tradeoffs
CS Fundamentals Index
Tier 1 -- Foundations
Object-Oriented Programming
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.
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?
2. How It Works
There are three fundamental loop shapes, and all languages provide some mix of them:
- Counted loop — iterate a known number of times (
for i in range(n)). - Condition loop — iterate while a condition holds (
while cond:). - 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.
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?
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 whyrange(n)gives0..n-1andfor (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 decrementsxwill 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 raiseRuntimeError: 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. Usefor x in xs:. If you also need the index, useenumerate(xs)(Python) orfor (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), JavaStream, 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
iafter a C-style for → loop body reboundi; don't.
- Loop runs one too few / one too many times → off-by-one; 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?
4. Language Differences
| Aspect | Python | Java | C++ |
|---|---|---|---|
C-style for(init; cond; step) | No — use range or while | Yes | Yes |
| For-each | for 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 iteration | yield | Iterator, Stream | Iterators, ranges (C++20) |
for ... else (runs if no break) | Yes | No | No |
| Unsigned underflow hazard | N/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.
A C++ developer writes the following loop to iterate backward over a vector and remove flagged elements:
During testing, the program hangs in an infinite loop. What is the most likely cause?for (size_t i = v.size() - 1; i >= 0; --i) { if (shouldRemove(v[i])) v.erase(v.begin() + i); }
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.
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?
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.
breakexits the innermost loop;continueskips 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 xsactually do?" — Callsiter(xs)once, thennext()repeatedly untilStopIteration. - "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
whileoverfor?" — 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.