skip to content

Iterators & Traversal

How collections are traversed — Iterator, ListIterator and Spliterator — and what happens when the collection changes underneath you. ConcurrentModificationException is the guaranteed follow-up question.

part ofJavaoverview, primer and where to startread it →
on this pageshow

explore

questions

15

What is a ConcurrentModificationException, and when does it typically occur?

level: juniorimportance: must knowfreq 78%

answer

  1. modCount vs expectedModCount
  2. structural = changes size
  3. usually single-threaded for-each bug
  4. fix: Iterator.remove / removeIf / copy
  5. fail-fast = throw immediately

basics

~20 s

It's an error Java throws when you change a collection's structure (add or remove items) while looping over it with a for-each loop or iterator. The classic case: removing an element from a list inside a for-each loop.

solid answer

~40 s

ConcurrentModificationException (CME) signals that a collection was structurally modified while it was being iterated, in a way the iterator didn't expect. 'Structural' means adding or removing elements (changing the size), not just updating an existing element's value. The most common cause has nothing to do with multiple threads: it's modifying the collection directly (e.g. list.remove(x)) inside a for-each loop over that same collection. The for-each loop uses an iterator under the hood, and that iterator detects the modification on its next next() or remove() call and throws. The fix is to either iterate over a copy, use Iterator.remove(), use removeIf(), or collect-then-modify. Despite the 'Concurrent' name, it is most often a single-threaded bug.

code

java · 15 lines
java
List<String> list = new ArrayList<>(List.of("a", "b", "c"));

// WRONG: throws ConcurrentModificationException
for (String s : list) {
    if (s.equals("b")) list.remove(s);
}

// RIGHT (Java 8+):
list.removeIf(s -> s.equals("b"));

// RIGHT (explicit iterator):
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("b")) it.remove();
}

go deeper

for a junior

Recognizes the classic 'remove inside a for-each' bug and knows the standard fix (Iterator.remove or removeIf). Knows CME is usually a single-threaded mistake, not a threading issue.

for a middle

Can explain modCount/expectedModCount and what 'structural modification' precisely means (size change, not value change). Lists multiple correct fixes and knows set() is safe.

for a senior

Explains why fail-fast exists (avoid silent wrong results), that detection is best-effort and not a concurrency guarantee, and when to switch to concurrent collections instead.

for a principal

Frames CME as a designed bug-detector, contrasts fail-fast vs weakly-consistent iterators as a deliberate library trade-off, and guides teams on choosing data structures for concurrent access patterns.

## The problem in one picture In Java, a **collection** is an object that holds a group of elements (a `List`, `Set`, `Map`, etc.). To visit every element you use an **iterator** — an object that walks the collection one element at a time via `hasNext()` and `next()`. The `for (T x : collection)` syntax (the **for-each loop**) is just syntactic sugar: the compiler turns it into an explicit iterator loop. **ConcurrentModificationException (CME)** is a `RuntimeException` (unchecked — you don't have to declare or catch it) thrown when the collection is **structurally modified** while an iterator is walking it. ## What 'structural modification' means A **structural modification** changes the *number* of elements — i.e. anything that adds or removes elements and thus changes `size()`: `add`, `remove`, `clear`, `addAll`, etc. **Not** structural: replacing an existing element's value in place, e.g. `list.set(i, newValue)` or `map.put(existingKey, newValue)` — these keep the size the same and do **not** trigger CME. ## The classic bug (single-threaded) ```java List<String> list = new ArrayList<>(List.of("a", "b", "c")); for (String s : list) { // hidden iterator if (s.equals("b")) { list.remove(s); // structural modification of the SAME list } } // throws ConcurrentModificationException on the next iteration ``` Notice: **no threads involved.** You modified the list through the *list* while iterating through the *iterator*; the iterator notices the size changed underneath it and refuses to continue, because continuing could skip elements or read garbage. ## How the iterator notices (the mechanism) Most JDK collections keep an internal counter, **`modCount`** (modification count), incremented on every structural change. When you create an iterator it snapshots that value into `expectedModCount`. On each `next()` (and `remove()`) the iterator runs `checkForComodification()`: if `modCount != expectedModCount`, the collection changed behind its back, so it throws CME. This is called a **fail-fast** iterator: it fails immediately and loudly rather than silently producing wrong results. ## Why fail fast at all? If the iterator quietly kept going after a removal, it could skip the element right after the removed one, or index past the end. Surfacing a clear exception at the point of misuse is far better than a subtle, data-dependent bug. CME is a **bug detector**, not a feature you should catch and ignore. ## The correct fixes 1. **`Iterator.remove()`** — the iterator's own remove keeps `expectedModCount` in sync: ```java Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().equals("b")) it.remove(); } ``` 2. **`Collection.removeIf(predicate)`** (Java 8+) — concise and safe. 3. **Iterate a copy, modify the original:** `for (String s : new ArrayList<>(list)) { ... }`. 4. **Collect then modify:** gather the to-remove items in a separate list, then `removeAll`. ## The 'Concurrent' in the name is misleading The same exception *can* fire when one thread iterates while another structurally modifies the collection — but fail-fast detection is **best-effort and not guaranteed** for true concurrency (the `modCount` read isn't synchronized). So you must never *rely* on CME for thread-safety; for genuine multi-threaded access use the concurrent collections (`ConcurrentHashMap`, `CopyOnWriteArrayList`) whose iterators are **fail-safe / weakly consistent** and never throw CME.

  • Does calling list.set(i, value) inside a for-each loop throw CME?
    No. set() replaces an existing element without changing the size, so it is not a structural modification and does not bump modCount. (Note: for-each gives you no index, so set is awkward there anyway; the point is set() itself is CME-safe.)
  • Is CME a checked or unchecked exception?
    Unchecked — it extends RuntimeException, so you are not required to declare or catch it. That's deliberate: it indicates a programming bug, not a recoverable condition.

Like counting people in a room from a clipboard list: if someone walks out (or in) mid-count, your tally is now wrong, so you stop and raise your hand rather than report a bad number.

saying these in an interview costs you the question

  • Thinking CME only happens with multiple threads — it's usually a single-threaded for-each bug.
  • Believing list.set() during iteration causes CME — it doesn't, because it isn't a structural modification.
  • Catching CME and ignoring/retrying instead of fixing the modify-while-iterating code.
  • Assuming the JVM guarantees CME on every concurrent modification — detection is best-effort only.

context

open as a page

What is the Iterator interface in Java, and what are its core methods?

level: juniorimportance: must knowfreq 75%

basics

~20 s

Iterator is an object that walks through a collection one element at a time. You call hasNext() to check if more elements remain, next() to get the next one, and remove() to delete the last element returned.

open as a page

How does a fail-fast iterator detect concurrent modification internally?

level: middleimportance: must knowfreq 62%

basics

~20 s

The collection keeps a counter called modCount that goes up every time you add or remove an element. When you make an iterator, it remembers that number. On each next() it compares them; if they differ, the collection changed behind its back, so it throws.

open as a page

You need to remove elements from a List while iterating it. What are the correct ways, and what are the pitfalls of each?

level: middleimportance: must knowfreq 70%

basics

~20 s

Don't call list.remove() inside a for-each loop — it throws an error. Instead use removeIf() with a condition, or use an explicit Iterator and call iterator.remove(). You can also loop over a copy of the list and remove from the original.

open as a page

How do you safely remove elements from a collection while iterating over it, and why is collection.remove() inside a loop dangerous?

level: middleimportance: must knowfreq 80%

basics

~10 s

Use the iterator's own remove() method, not the collection's. Calling list.remove() while looping with a for-each usually throws ConcurrentModificationException because the collection changed behind the iterator's back.

open as a page

What does ListIterator add over a plain Iterator, and when would you use it?

level: middleimportance: should knowfreq 60%

basics

~10 s

ListIterator works on lists and can move both directions (hasNext/next and hasPrevious/previous). It can also add() new elements and set() (replace) the current one during traversal, and it tells you the index via nextIndex()/previousIndex().

open as a page

What is the relationship between Spliterator, Collection.spliterator(), Stream, and StreamSupport?

level: middleimportance: should knowfreq 40%

basics

~20 s

A Spliterator is the low-level thing that walks (and can split) the elements of a source. Every Collection can make one via spliterator(), and Stream/parallelStream are built on top of it. StreamSupport.stream(...) is the helper that turns any Spliterator into a Stream.

open as a page

What is a Spliterator in Java, and how does it differ from a classic Iterator?

level: middleimportance: should knowfreq 55%

basics

~20 s

A Spliterator is an object that walks over the elements of a source (like a list) one at a time, and can also split itself in two so the halves can be processed in parallel. An Iterator can only walk forward, never split.

open as a page

Contrast fail-fast and fail-safe (weakly consistent) iterators, and give examples of collections that use each.

level: seniorimportance: should knowfreq 58%

basics

~20 s

Fail-fast iterators (like ArrayList's and HashMap's) throw an error if the collection changes during iteration. Fail-safe iterators (like ConcurrentHashMap's or CopyOnWriteArrayList's) don't throw — they work over a snapshot or tolerate changes, but may not show the very latest updates.

open as a page

Explain the difference between fail-fast and fail-safe (weakly consistent) iterators in Java, with examples.

level: seniorimportance: should knowfreq 55%

basics

~10 s

Fail-fast iterators (like ArrayList's or HashMap's) throw ConcurrentModificationException if the collection changes during iteration. Fail-safe iterators (like CopyOnWriteArrayList's or ConcurrentHashMap's) iterate over a snapshot or tolerate changes without throwing.

open as a page

What are Spliterator characteristics, and how does the Streams API use flags like ORDERED, SIZED, DISTINCT, SORTED, and IMMUTABLE?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Characteristics are flags a Spliterator reports about its data — for example that the elements are in order, that the exact count is known, that they are all unique, or that the source can't change. The stream pipeline reads these flags to skip unnecessary work.

open as a page

How does trySplit() work, and what makes a good split for parallel streams?

level: seniorimportance: should knowfreq 48%

basics

~20 s

trySplit() tries to hand off about half of the remaining elements to a brand-new Spliterator and keeps the rest for itself, so two threads can work on the two halves at once. If it can't usefully split, it returns null.

open as a page

How would you make your own class iterable with the for-each loop, and what is the contract you must satisfy?

level: seniorimportance: nice to knowfreq 45%

basics

~10 s

Implement the Iterable<T> interface and provide an iterator() method that returns an Iterator<T>. The iterator needs hasNext() and next() (and optionally remove()). Then your object works in a for-each loop.

open as a page

Why did the Java library designers make most collections fail-fast rather than fail-safe, and when does that decision break down?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Most collections are used by one thread, so the designers made iteration fast and made wrong usage fail loudly and early — that catches bugs cheaply. It breaks down when collections are shared across threads, where you should use concurrent collections instead.

open as a page

How would you write a custom Spliterator, and when is doing so actually worth it?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

You implement tryAdvance (process one element), trySplit (hand off about half for parallelism), estimateSize (how many are left), and characteristics (the flags). You only bother when you have a custom data source that the built-in Collection/Stream tools can't traverse or parallelize well.

open as a page