Adetayo Akinsanya unkletayo.dev
Engineering / Java Collections From Scratch • Part 5 of 26 Published

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:

  1. Whenever add(), remove(), or clear() executes, modCount++ runs.
  2. When an Iterator instance is created, it captures a snapshot of modCount into a field named expectedModCount.
  3. On every call to it.next() or it.remove(), the iterator compares modCount against expectedModCount.
  4. If modCount != expectedModCount, another operation modified the list structure mid-loop. The iterator immediately throws ConcurrentModificationException.
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 for loops are compiler syntactic sugar over Iterable and Iterator.
  • modCount counts structural mutations (adds, removes, clears).
  • Discrepancies between modCount and expectedModCount trigger ConcurrentModificationException.

References & Further Reading

  1. OpenJDK Repository. OpenJDK 21 Source Code: java.util.ArrayDeque. GitHub.
  2. Sedgewick, R., & Wayne, K. (2011). Algorithms (4th Edition) — Chapter 1.3: Bags, Queues, and Stacks. Addison-Wesley.
  3. Oracle Corporation. Java SE 21 API Documentation: java.util.ArrayDeque. Oracle Docs.

Up Next in Series →

Part 6: Building a Custom Java Stack: LIFO Mechanics & Why Legacy Stack is Broken

Continue to Part 6 →