Java Iterator and modCount: How Fail-Fast Iteration Prevents Data Corruption
Enhanced for-loop desugaring, ConcurrentModificationException, and safe removal.
Part 5 in Series — Catch up on the previous article: Java LinkedList Internals: Building a Doubly Linked List from Scratch (Part 4) before diving into this post.
Why You Need This in Real Life
You’ve probably written a clean for-each loop like this a hundred times to clean up stale records in a background job:
for (Order order : activeOrders) {
if (order.isExpired()) {
activeOrders.remove(order); // Looks harmless, right?
}
}
It looks elegant. It reads like natural English. But the second an expired order triggers activeOrders.remove(order), Java throws a ConcurrentModificationException and crashes your production job.
Your first instinct might be to curse the JVM for being pedantic. But if Java didn’t throw that exception, something far worse would happen: as elements shift left in memory to fill the gap, your loop index silently skips the very next order in line. Unpaid reservations sit active in production forever, leaving your engineering team wondering why expired inventory never reconciled.
To save you from silent data corruption, the JVM uses fail-fast iterators guarded by a low-level structural counter called modCount. Understanding this mechanism is the secret to writing safe, high-throughput collection code.
Enhanced For-Loop Desugaring
The Iterable<T> interface defines a single method contract:
public interface Iterable<T> {
Iterator<T> iterator();
}
When compiled, an enhanced for loop desugars into this explicit bytecode pattern:
Iterator<Order> it = activeOrders.iterator();
while (it.hasNext()) {
Order order = it.next();
if (order.isExpired()) {
// Calling activeOrders.remove(order) here triggers modCount mismatch!
}
}
How modCount Tracks Structural Mutations
Every collection class maintains a protected integer field named modCount:
- Whenever
add(),remove(), orclear()executes,modCount++runs. - When an
Iteratorinstance is created, it captures a snapshot ofmodCountinto a field namedexpectedModCount. - On every call to
it.next()orit.remove(), the iterator comparesmodCountagainstexpectedModCount. - If
modCount != expectedModCount, another operation modified the list structure mid-loop. The iterator immediately throwsConcurrentModificationException.
LIST STATE ITERATOR SNAPSHOT
modCount: 5 expectedModCount: 5
User calls list.remove(item):
modCount: 6 ---------------------> expectedModCount: 5
MATCH FAIL! Throw Exception!
Adding Fail-Fast Iteration to MyArrayList
Here is our updated MyArrayList implementing Iterable<T> and the inner Iterator class:
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyArrayList<T> implements Iterable<T> {
private Object[] elementData;
private int size;
private int modCount = 0; // Tracks structural changes
public MyArrayList() {
this.elementData = new Object[10];
}
public void add(T element) {
modCount++; // Structural change!
ensureCapacity(size + 1);
elementData[size++] = element;
}
@SuppressWarnings("unchecked")
public T remove(int index) {
checkBounds(index);
modCount++; // Structural change!
T oldValue = (T) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return oldValue;
}
@Override
public Iterator<T> iterator() {
return new Itr();
}
private class Itr implements Iterator<T> {
private int cursor = 0;
private int lastRet = -1;
private int expectedModCount = modCount;
@Override
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
@Override
public T next() {
checkForComodification();
int i = cursor;
if (i >= size) {
throw new NoSuchElementException();
}
cursor = i + 1;
lastRet = i;
return (T) elementData[lastRet];
}
@Override
public void remove() {
if (lastRet < 0) {
throw new IllegalStateException();
}
checkForComodification();
try {
MyArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // Synchronize expected count after safe remove!
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
private void checkForComodification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
private void ensureCapacity(int minCapacity) {
if (minCapacity > elementData.length) {
elementData = java.util.Arrays.copyOf(elementData, elementData.length * 2);
}
}
private void checkBounds(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
}
The Correct Way to Remove Items Mid-Loop
To safely delete items while iterating without crashing, use the iterator’s own it.remove() method or removeIf():
// Method 1: Using Iterator.remove()
Iterator<Order> it = activeOrders.iterator();
while (it.hasNext()) {
Order order = it.next();
if (order.isExpired()) {
it.remove(); // Safely updates cursor and expectedModCount!
}
}
// Method 2: Modern Java 8+ removeIf
activeOrders.removeIf(Order::isExpired);
Iterator.remove() calls list.remove(), increments modCount, and updates expectedModCount = modCount inside the iterator state, allowing the loop to proceed safely.
Quick Summary
- Enhanced
forloops are compiler syntactic sugar overIterableandIterator. modCountcounts structural mutations (adds, removes, clears).- Discrepancies between
modCountandexpectedModCounttriggerConcurrentModificationException.
References & Further Reading
- OpenJDK Repository. OpenJDK 21 Source Code:
java.util.ArrayDeque. GitHub. - Sedgewick, R., & Wayne, K. (2011). Algorithms (4th Edition) — Chapter 1.3: Bags, Queues, and Stacks. Addison-Wesley.
- Oracle Corporation. Java SE 21 API Documentation:
java.util.ArrayDeque. Oracle Docs.
Part 6: Building a Custom Java Stack: LIFO Mechanics & Why Legacy Stack is Broken
Continue to Part 6 →