Dataslope logoDataslope

Iteration Patterns

For-each, explicit iterators, ListIterator, streams, and the unwritten rules about modifying a collection while iterating it

There is more than one way to walk a collection in Java, and the right one depends on what you actually need: just read each element? remove some as you go? insert new ones? walk backwards? process the whole thing as a pipeline?

This chapter is a tour of the five iteration patterns you'll use every week, plus the single most common bug each one introduces.

1. Enhanced for-each

The for-each loop is sugar over an Iterator. It is the right tool for "do something with each element, in order":

Code Block
Java 8 (Update 492)

The compiler rewrites for (E x : c) into roughly:

Iterator<E> it = c.iterator();
while (it.hasNext()) {
    E x = it.next();
    /* body */
}

That has one immediate consequence: you cannot remove elements from the collection inside a for-each loop. Doing so triggers ConcurrentModificationException on the next it.hasNext().

2. Explicit Iterator and Iterator.remove()

When you need to remove while iterating, use the explicit Iterator form so you can call it.remove() — the only safe way to mutate during iteration:

Code Block
Java 8 (Update 492)

Even better, Java 8 added removeIf on every Collection, which is the modern one-liner:

Code Block
Java 8 (Update 492)

3. ListIterator: bidirectional, plus add()

Lists support listIterator(), which is Iterator with superpowers: you can walk backwards (hasPrevious, previous), replace the current element (set), and insert a new element at the cursor (add):

Code Block
Java 8 (Update 492)

ListIterator is what makes "edit in place" patterns possible on list-backed structures.

4. forEach with a Consumer

Every Iterable has a default forEach(Consumer<? super E>). It reads naturally when the action is a simple, terminal side effect:

Code Block
Java 8 (Update 492)

forEach does not let you stop early — that's a for with a break's job. And, as with the enhanced for, you cannot modify the collection from inside the action.

5. Streams: iteration as a pipeline

Streams are the declarative form of iteration. You describe what should happen to each element (filter, map, sorted, distinct, flatMap, ...) and end with a terminal operation (toList, count, reduce, forEach, ...). The library decides how.

Code Block
Java 8 (Update 492)

The same logic with a manual loop is twice as long and twice as mistake-prone:

Code Block
Java 8 (Update 492)

Use a stream when the loop body is a clear pipeline of filter → transform → collect. Use an explicit loop when the body has real control flow (early returns, complex branching, state across iterations).

ConcurrentModificationException: the real villain

The exception's name is misleading: it has nothing to do with threads. It is raised by Iterator when it notices the structural modification count of the collection changed since the iterator was created — i.e. you added or removed something behind its back.

The fix is always one of:

  1. Use the iterator's own remove() (or removeIf for whole-collection deletes).
  2. Collect what you want to remove during iteration, then remove afterwards.
  3. Iterate a copy: for (E x : new ArrayList<>(xs)) { xs.remove(...); }.

Multi-file example: filter a feed with three techniques

Code Block
Java 8 (Update 492)

Three idioms, same output — and the stream version is the easiest to read in five months' time.

Practice

Challenge
Java 8 (Update 492)
Safe removal during iteration

Complete Pruner.removeShorterThan(List<String> words, int minLen) so it removes every word shorter than minLen without triggering ConcurrentModificationException. Use either Iterator.remove() or removeIf.

Expected output:

[alpha, gamma, delta]

Test your understanding

QuestionSelect one

Why does this loop throw ConcurrentModificationException?

for (Integer n : list) if (n % 2 == 0) list.remove(n);

Because list.remove(Object) is O(n)

Because the enhanced-for uses an Iterator, and modifying the underlying list via list.remove invalidates the iterator's internal modCount

Because list is unmodifiable

Because integers are autoboxed

QuestionSelect one

Which iteration form lets you insert new elements at the cursor while walking a List?

Enhanced for-each

forEach(Consumer)

Plain Iterator

ListIterator

QuestionSelect one

When is a stream pipeline the better choice over an explicit loop?

Always, because streams are faster

When the body needs break or return

When the body is a straightforward pipeline of filter / transform / collect with no early exit

When you need to mutate the source collection

On this page