How do you avoid ConcurrentModificationException when removing elements from a list?

ConcurrentModificationException happens when you change a collection's structure while iterating over it with a for-each loop. The safe ways are to remove through the Iterator, use removeIf(), build a new filtered list, or use a concurrent collection.

Key Points: • The for-each loop uses an Iterator internally. Calling list.remove() directly changes modCount, and the next call to next() detects it and throws. • iterator.remove() removes the current element and keeps the iterator in sync. • list.removeIf(predicate) (Java 8) is the shortest and clearest option. • stream().filter(...).toList() creates a new list instead of changing the original. • The exception can happen in a single thread. It is about modifying during iteration, not only about multithreading.

Code Example:

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

public class RemoveWhileIterating {

    public static void main(String[] args) {
        // 1. Iterator.remove()
        List<Integer> a = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
        Iterator<Integer> it = a.iterator();
        while (it.hasNext()) {
            if (it.next() % 2 == 0) it.remove();
        }
        System.out.println("Iterator.remove(): " + a);

        // 2. removeIf (Java 8+)
        List<Integer> b = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
        b.removeIf(n -> n % 2 == 0);
        System.out.println("removeIf(): " + b);

        // 3. New list with streams (original unchanged)
        List<Integer> c = List.of(1, 2, 3, 4, 5, 6);
        System.out.println("filter(): " + c.stream().filter(n -> n % 2 != 0).toList());

        // 4. Index loop going backwards also works
        List<Integer> d = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
        for (int i = d.size() - 1; i >= 0; i--) {
            if (d.get(i) % 2 == 0) d.remove(i);
        }
        System.out.println("Backward loop: " + d);
    }
}

Output:

Iterator.remove(): [1, 3, 5] removeIf(): [1, 3, 5] filter(): [1, 3, 5] Backward loop: [1, 3, 5]

Good to Know: Interviewers sometimes show code that removes the second-to-last element inside a for-each loop and ask why it doesn't throw. After that removal, hasNext() returns false because the cursor equals the new size, so the loop ends before next() can check modCount. It's a well-known trick question.

Interview Tip: A concise interview answer is:

"It's thrown when a collection is structurally changed during a for-each loop, because the iterator detects the changed modification count. I avoid it by removing through iterator.remove(), or more simply with removeIf() from Java 8, or by creating a new filtered list with streams. For collections shared between threads, I use a concurrent collection."

Previous question: What is the difference between fail-fast and fail-safe iterators in Java?

Next question: Can a HashMap have a null key? How is it stored?

More Java Collections interview questions

All Java Collections questions