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
- Default settings — Removal inside an enhanced for statement. An exception is thrown.
- for statement with an index, from the front — No exception is thrown, but one 2 is left.
- Removing from the back — The elements that shift are on the side that was already visited, so there is no problem.
- Iterator.remove() — Removing through the iterator is safe.
- removeIf() — This is the simplest and safest way.
- Removal in a for statement on a CopyOnWriteArrayList — No exception is thrown. The loop iterates over the old array from when it was created.
Running with the default settings
- The list is [1, 2, 2, 3, 4]. The code tries to remove the two elements whose value is 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).
- Element 1 is checked: it is not 2, so the loop moves on.
- 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.
- 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).
- Final list: [1, 2, 3, 4] (stopped by an exception).
Result of this run
- Despite its name, this exception is thrown even when there is only one thread. It occurs when a collection is modified during iteration without going through the iterator. The iterator detects the change by comparing modification counts and fails immediately.
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