Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Java › Collections

Removing during iteration

What happens when you remove elements inside a loop

The code iterates over the list [1, 2, 2, 3, 4] and tries to remove the elements whose value is 2. Depending on how it removes them, an exception is thrown, or one element is skipped with no exception at all.

Checked against real execution · Checked by running 10 combinations on JDK 21

Concepts in this lab

Fail-fast iterator
This is an iterator that throws ConcurrentModificationException if the collection is structurally modified while it is being iterated (detection is on a best-effort basis, so the exception is not guaranteed to be thrown every time). The design fails immediately to expose the problem, which is better than continuing with a wrong result without knowing about the change. To remove elements during iteration, use the iterator's remove() or removeIf().
ArrayList
This is a list that keeps an array inside and moves the elements to a larger array when the array is full. Access to an element by index is fast at O(1), and adding at the end is also O(1) amortized. Inserting or removing in the middle takes O(n), because the elements after that position must each be shifted by one position.
ConcurrentHashMap
This is a hash map that is safe for several threads to use at the same time. It does not lock the whole map; it controls access per bucket, which gives high concurrency, and lookups run without a lock. It does not allow null keys or null values. For a compound action such as "put if absent", you must use a method that guarantees atomicity, such as putIfAbsent or compute.
Thread safety
This is the property of working correctly even when several threads use something at the same time. Code is thread-safe if there is no shared state, if the state is immutable, or if access is properly synchronized. A Spring singleton bean is used by many request threads together, so it must be made thread-safe (usually by keeping no state).

Try these

Running with the default settings

  1. The list is [1, 2, 2, 3, 4]. The code tries to remove the two elements whose value is 2.
  2. An iterator is created (an enhanced for statement also uses an iterator internally). The iterator remembers the modification count of the list at this point (0).
  3. Element 1 is checked: it is not 2, so the loop moves on.
  4. The element 2 is read. list.remove() removes it directly from the list. The list is now [1, 2, 3, 4]. The modification count of the list has gone up to 1, but the iterator does not know that.
  5. The iterator is about to read the next element, but the modification count of the list (1) differs from the value the iterator remembered (0). This means that the list changed without the iterator knowing, so rather than produce a wrong result, the iterator throws a ConcurrentModificationException immediately (fail-fast).
  6. Final list: [1, 2, 3, 4] (stopped by an exception).

Result of this run

Code

List<Integer> list = new ArrayList<>(List.of(1, 2, 2, 3, 4));

for (Integer x : list) {
    if (x == 2) list.remove(x);          // modifies the list directly during iteration
}

Assumptions of this model

The final list and the exception were checked for all 10 combinations against the result of running the same code on JDK 21 on this PC. Removal inside an enhanced for statement sometimes ends without an exception, depending on the position of the element that is removed (when the second-to-last element is removed), but such code relies on luck and must not be used.

Previous lab: Stack, queue, priority queue · Next lab: Sorting and Comparator

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these