Module 7 · Collections & Generics ⏱ 18 min

Iteration: for-each, Iterators, ConcurrentModification

By the end of this lesson you will be able to:
  • Explain that for-each is shorthand for stepping through a collection with an Iterator
  • Read the Iterator methods hasNext, next, and remove
  • Predict when modifying a collection during iteration throws ConcurrentModificationException

You have used the for-each loop since the very first collections lesson: for (String f : fruits) walks every element with no index to manage. It is the safe default, and it hides something important. Underneath, for-each is not magic — it is shorthand for a small machine called an iterator that steps through the collection one element at a time. Most days you never need to know that.

The day you try to remove an element while you loop, it suddenly matters a great deal, because that is when an unmodified for-each explodes with a ConcurrentModificationException. The name says concurrent, but the trap springs even in a single thread — it has nothing to do with parallelism. This lesson makes the hidden machinery visible: how iteration actually works, why editing during a loop is dangerous, and the two clean ways to do it safely.

For-each is an iterator in disguise

Read the colon in for (String f : fruits) as infor each string f in fruits. The loop runs the body once per element, handing you each in turn, and it works identically over a List, a Set, or an array because all of them are iterable.

When the compiler meets a for-each over a collection, it rewrites it. The rewrite calls fruits.iterator() to obtain an Iterator, then loops while that iterator still has a next element, calling next() to fetch each one. You never see those calls, but they happen on every pass — and that hidden iterator is what tracks how far through the collection you have read. It is also the very thing that blows up if the collection changes underneath it.

flowchart LR
  FE["for each s in list"] -->|"compiles to"| IT["obtain an Iterator"]
  IT --> LOOP["while hasNext()"]
  LOOP --> NX["next() returns one element"]
  NX --> LOOP
  style FE fill:#3776ab,color:#fff
  style IT fill:#c2410c,color:#fff
A for-each loop is rewritten to obtain an Iterator and repeat next() while hasNext() is true. The loop body runs once per element.

The indexed loop, and the Iterator up close

Before for-each, you walked a list by counting: for (int i = 0; i < list.size(); i++) { ... list.get(i) ... }. The index i is entirely in your hands, which is power and danger in equal measure — get the bound wrong and you read past the end, or off-by-one your way into skipping the first or last element. For-each removes that footgun by managing the position for you.

When you need finer control you drop down to an Iterator directly. It exposes two methods for reading: hasNext() asks is there another element?, and next() fetches it and advances. A while (it.hasNext()) { String s = it.next(); } loop is exactly what for-each compiles to, written out by hand. The reason to write it yourself is a third method iterators offer: remove().

flowchart LR
  S["start"] --> H["hasNext()?"]
  H -->|"yes"| N["next() gives the next element"]
  N --> H
  H -->|"no"| D["loop ends"]
  style H fill:#c2410c,color:#fff
  style N fill:#3776ab,color:#fff
An Iterator is a tiny state machine: ask hasNext(), take next() while true, and stop when false.
Main.java — a for-each loop adding up a list of numbers. Real, compiling Java.
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(10);
        nums.add(20);
        nums.add(30);
        int sum = 0;
        for (int n : nums) {
            sum += n;
        }
        System.out.println("sum: " + sum);
    }
}

The trap: editing while you iterate

Here is the surprise that bites everyone once. You loop over a list with for-each, and inside the loop you decide some elements should go: if (s.length() == 0) { list.remove(s); }. The program compiles. It runs. And partway through, it crashes with a ConcurrentModificationException.

The reason is that hidden iterator. It keeps track of how many elements it has handed you, and the collection keeps a count of how many times it has been structurally changed. When next() runs, the two are compared; if the collection changed outside the iterator — by your direct list.remove — the counts disagree and the iterator refuses to continue, because it can no longer guarantee it will not skip or repeat an element. Throwing is its way of failing loudly instead of silently corrupting your loop.

flowchart TD
  LOOP["removing during a for-each"] --> Q["who performs the remove?"]
  Q -->|"list.remove()"| BAD["ConcurrentModificationException"]
  Q -->|"it.remove()"| GOOD["safe, counts stay in sync"]
  Q -->|"collect, remove after loop"| GOOD2["safe, no exception"]
  style BAD fill:#b45309,color:#fff
  style GOOD fill:#3776ab,color:#fff
  style GOOD2 fill:#3776ab,color:#fff
Removing during a for-each is safe only through the iterator itself, or by deferring the removal until after the loop.
Main.java — removing through the iterator while iterating. it.remove() keeps the counts in sync, so no exception is thrown. Real, compiling Java.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        Iterator<Integer> it = nums.iterator();
        while (it.hasNext()) {
            int n = it.next();
            if (n == 2) {
                it.remove();   // safe: removes through the iterator
            }
        }
        for (int n : nums) {
            System.out.println(n);
        }
    }
}
Exercise

For-each over a list. The loop adds every element to a running total. Predict the exact output (one line).

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(10);
        nums.add(20);
        nums.add(30);
        int sum = 0;
        for (int n : nums) {
            sum += n;
        }
        System.out.println("sum: " + sum);
    }
}
Exercise

When the compiler meets for (String s : list) over a collection, what does it rewrite the loop to use?

Exercise

Inside for (String s : list) { ... }, you call list.remove(s). What happens?

Exercise

Safe removal through the iterator. The value 2 is removed via it.remove(), then the list is printed. Predict the exact output (two lines).

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        Iterator<Integer> it = nums.iterator();
        while (it.hasNext()) {
            int n = it.next();
            if (n == 2) {
                it.remove();
            }
        }
        for (int n : nums) {
            System.out.println(n);
        }
    }
}
Exercise

An indexed loop. The position i is printed alongside each element. Predict the exact output (three lines).

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("cat");
        words.add("dog");
        words.add("bird");
        for (int i = 0; i < words.size(); i++) {
            System.out.println(i + ": " + words.get(i));
        }
    }
}

Recap

  • For-each (for (T x : coll)) is shorthand for stepping through a collection with an Iterator.
  • An Iterator offers hasNext(), next(), and remove() — write the loop yourself when you need that control.
  • Calling the collection's own remove() inside a for-each throws ConcurrentModificationException, because the iterator detects the outside change.
  • Remove safely via the iterator's remove(), or by collecting items and removing them after the loop.
  • The indexed for loop still works when you genuinely need a position, but for-each avoids the off-by-one errors an index invites.

That closes the core of working with collections. Next you will give your own types a natural sort order with Comparable and Comparator.

Checkpoint quiz

Why does list.remove(s) inside a for-each throw ConcurrentModificationException?

Which is a safe way to remove elements while iterating?

Go deeper — technical resources