liveBook · Manning

Loop

As explained in 100 Java Mistakes and How to Avoid Them

A for loop is a Java loop statement with initialization, condition, and iteration components that controls repeated execution.

A Java for loop repeats work while its condition holds. The classic form is especially useful for numeric ranges and indexed access, but its initialization, bound, and update must describe the same progression. Small disagreements between them can select the wrong elements, skip work, run zero times, or continue far longer than intended. Treat a loop as behavior that needs tests, not as routine punctuation. section 3.0 section 3.12

For an ascending half-open range, `for (int i = lo; i < hi; i++)` visits `lo` through `hi - 1`. Reversing that range requires changing all three directional choices: start at `hi - 1`, retain the lower endpoint with `i >= lo`, and decrement with `i--`. Changing only the start or bound produces off-by-one behavior; leaving `i++` can make the loop execute zero times or run until integer overflow. section 3.6

for (int i = hi - 1; i >= lo; i--)

A descending range includes `hi - 1` and `lo`; the decrement agrees with the `i >= lo` condition. section 3.6

Use these safeguards when writing classic loops:

  • Generate ordinary range loops with an IDE when possible, then modify them deliberately rather than reconstructing their parts from memory. section 3.12
  • When reversing a loop, verify the initial value, comparison operator, and update together. IDE reverse-direction actions and reverse-loop templates reduce mismatches. section 3.6
  • Test at least two iterations. A missing state change can stay hidden when typical inputs execute the body once or not at all. section 3.12
  • Treat an inclusive `i <= hi` bound cautiously when `hi` may be `Integer.MAX_VALUE` or `Long.MAX_VALUE`; incrementing can overflow and make the condition true indefinitely. section 3.7

Static analysis can catch a direction mismatch such as `i >= lo; i++`: IntelliJ IDEA reports a loop that executes zero or billions of times, and SonarLint reports S2251. These tools cannot reliably reject every suspicious endpoint, because an unusual initial value or bound may be intentional. Review boundary semantics and cover extreme values with tests rather than treating a clean inspection result as proof. section 3.6 section 3.7

Iteration and collection mutation

An enhanced for-each loop uses an iterator. Non-concurrent collections normally detect structural modification outside that iterator and throw `ConcurrentModificationException` on a later `next()` call; this is fail-fast behavior. An indexed loop has no iterator and may instead continue over the changed list, so replacing for-each with indexing changes both mechanics and risks. Do not select indexed loops merely to avoid the exception; make the intended modification strategy explicit. section 8.8

List<String> list = new ArrayList<>(List.of("a", "b", "c"));
for (int i = 0; i < list.size(); i++) {
  String s = list.get(i);
  System.out.println(s);
  if (s.equals("b")) {
    list.add("x");
  }
}

An indexed loop can observe an element appended during traversal because it does not use the collection's iterator. section 8.8

A loop is also a practical alternative when a Stream API pipeline needs side effects that do not fit its operations naturally. Rather than relying on side effects in intermediate stream stages, use a loop when it expresses the required sequencing more directly. Conversely, `IntStream.rangeClosed(lo, hi)` can avoid the inclusive-maximum overflow corner case, though its overhead matters in performance-critical code. section 9.2 section 3.7

Loops can remove recursion depth as a failure mode. Java does not guarantee tail-recursion elimination, so a tail-recursive operation on large input may overflow the stack. Rewrite tail recursion as iteration manually or with an IDE action when call depth could exceed several hundred; non-tail recursion may require an explicit heap-based structure such as `ArrayDeque` to retain intermediate state. section 5.4.1 section 5.5

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Explains range direction, bounds, overflow, iterator behavior, tests, and tool diagnostics without specifying loop bytecode internals.
Examples
Uses descending ranges, inclusive maximum bounds, mutated ArrayLists, Stream pipelines, and tail-recursive string search.
What you can do after
Diagnose/debug with it by testing boundaries, spotting direction mismatches, and choosing safer iteration forms.
How it connects
Connects loops with while loops, Stream API, iterators, ConcurrentModificationException, recursion, and ArrayDeque.

Also explained in

Compare how 27 books explain loop →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.