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)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 xJava's for-each over an Iterable compiles to calls on Iterator.hasNext() / Iterator.next() (over an array, it compiles to an indexed loop). 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.
The update makes the loop stop
Integer division repeatedly reduces n until the condition n > 1 becomes false.
The loop starts with one hundred and runs while n is greater than one.
Text version
- The loop starts with one hundred and runs while n is greater than one.
- Halving n changes the value that controls whether the loop repeats.
- Integer division rounds down, so twenty-five becomes twelve and three becomes exactly one.
- At one, the condition is false, so the loop exits.
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
The upper bound is excluded
range(5) prints 0 to 4. Bounds are half-open, [start, end): the start is included and the end is excluded.
Range starts at zero because no other start value is given.
Text version
- Range starts at zero because no other start value is given.
- Each value is printed before the loop asks for the next one.
- Four is the last value, because range of five ends at five minus one.
- The upper bound is excluded, so five is never printed and the loop runs five times.
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:
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?
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.
Choose iteration by memory needs
List comprehensions build all results; generators compute them on demand and allow consumers to stop early.
A list comprehension builds the full list, while a generator computes elements on demand.
Text version
- A list comprehension builds the full list, while a generator computes elements on demand.
- Processing generator results one at a time avoids storing a full result list.
- A lazy generator stops producing results when you stop requesting them.
- Use an eager list for small data, and a lazy generator for big data pipelines.
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).