Definition
- for-each + remove: the for-each loop uses an iterator internally; calling
list.remove()incrementsmodCountwithout updating the iterator'sexpectedModCount, causing CME on the nextnext()call - iterator.remove(): safe because it increments both
modCountandexpectedModCount - removeIf(predicate) (Java 8): iterates internally and calls iterator.remove() for matching elements; clean one-liner
- Stream filter: creates a new collection, leaving the original unchanged
- Map removal:
map.entrySet().removeIf(e -> condition)safely removes map entries
A bank clerk (iterator) reviews transaction forms in a stack. If someone else pulls a form from the stack (list.remove), the clerk loses his place and panics (CME). If the clerk himself pulls a form as he reviews it (iterator.remove), he knows exactly what happened and continues safely. removeIf is like telling the clerk: "Pull out all forms marked CLOSED" – he does it systematically without losing his place.
Understanding the Concepts
How do you safely remove elements during iteration?
The most common mistake in Java collections is modifying a list inside a for-each loop.
package com.bank.demo;
import java.math.BigDecimal;
import java.util.*;
public class SafeRemovalDemo {
record Account(String id, String status, BigDecimal balance) {}
public static void main(String[] args) {
List<Account> accounts = new ArrayList<>(List.of(
new Account("SAV2001", "ACTIVE", new BigDecimal("150000")),
new Account("CLOSED-001", "CLOSED", BigDecimal.ZERO),
new Account("CUR3001", "ACTIVE", new BigDecimal("75000")),
new Account("CLOSED-002", "CLOSED", BigDecimal.ZERO)
));
// WRONG: for-each + remove → CME
try {
for (Account acc : accounts) {
if (acc.status().equals("CLOSED")) {
accounts.remove(acc);
}
}
} catch (ConcurrentModificationException e) {
System.out.println("CME caught: " + e.getClass().getSimpleName());
}
// FIX 1: removeIf (Java 8) – BEST
accounts = new ArrayList<>(List.of(
new Account("SAV2001", "ACTIVE", new BigDecimal("150000")),
new Account("CLOSED-001", "CLOSED", BigDecimal.ZERO),
new Account("CUR3001", "ACTIVE", new BigDecimal("75000")),
new Account("CLOSED-002", "CLOSED", BigDecimal.ZERO)
));
accounts.removeIf(acc -> acc.status().equals("CLOSED"));
System.out.println("removeIf result: " + accounts.size() + " accounts");
// FIX 2: Iterator.remove()
accounts = new ArrayList<>(List.of(
new Account("SAV2001", "ACTIVE", new BigDecimal("150000")),
new Account("CLOSED-001", "CLOSED", BigDecimal.ZERO)
));
Iterator<Account> it = accounts.iterator();
while (it.hasNext()) {
if (it.next().status().equals("CLOSED")) {
it.remove();
}
}
System.out.println("Iterator.remove result: " + accounts.size() + " accounts");
}
}
CME caught: ConcurrentModificationException
removeIf result: 2 accounts
Iterator.remove result: 1 accounts
How does removeIf work on Maps?
Maps do not have a direct removeIf method, but entrySet().removeIf() works because entrySet() returns a view backed by the map.
package com.bank.service;
import java.math.BigDecimal;
import java.util.*;
public class MapRemoveIfDemo {
public static void main(String[] args) {
Map<String, BigDecimal> balances = new HashMap<>();
balances.put("SAV2001", new BigDecimal("150000"));
balances.put("DORMANT-001", BigDecimal.ZERO);
balances.put("CUR3001", new BigDecimal("75000"));
balances.put("DORMANT-002", BigDecimal.ZERO);
// Remove all zero-balance accounts
balances.entrySet().removeIf(e -> e.getValue().compareTo(BigDecimal.ZERO) == 0);
System.out.println("After cleanup: " + balances);
// Remove by key pattern
balances.keySet().removeIf(key -> key.startsWith("DORMANT"));
System.out.println("After key filter: " + balances);
}
}
After cleanup: {SAV2001=150000, CUR3001=75000}
After key filter: {SAV2001=150000, CUR3001=75000}
What are common mistakes?
Mistake 1: Using remove(index) in a forward loop
// WRONG: indices shift after each removal
for (int i = 0; i < list.size(); i++) {
if (condition(list.get(i))) {
list.remove(i); // next element shifts to index i, gets skipped!
}
}
// FIX: iterate backward
for (int i = list.size() - 1; i >= 0; i--) {
if (condition(list.get(i))) {
list.remove(i); // elements after i don't matter (already processed)
}
}
// BEST: use removeIf
list.removeIf(e -> condition(e));
Mistake 2: Assuming CME means multi-threading
// CME happens in SINGLE-threaded code too!
// It's about modifying during iteration, not about threads.
for (String s : list) {
list.add("new"); // CME! Single thread.
}
What should a senior engineer know?
- removeIf is optimized for ArrayList. ArrayList's removeIf implementation uses a bitset to mark elements for removal, then shifts remaining elements in one pass. This is more efficient than calling iterator.remove() n times (which shifts elements on each removal).
- removeIf returns boolean. It returns true if any elements were removed, false otherwise. Useful for logging: if (accounts.removeIf(condition)) log.info("Cleaned up accounts").
- ConcurrentHashMap.entrySet().removeIf(). This is thread-safe. Multiple threads can call removeIf concurrently without external synchronization.
What are common interview follow-up questions?
- "Why does for-each + remove throw CME?" – for-each uses an iterator; list.remove changes modCount without updating expectedModCount.
- "What is the cleanest way to remove elements?" – removeIf(predicate).
- "How do you remove from a Map during iteration?" – map.entrySet().removeIf().
- "Is removeIf thread-safe?" – On ConcurrentHashMap, yes. On ArrayList, no.
Visual Overview
for-each + remove (WRONG):
for (s : list) → uses iterator internally
list.remove(s) → modCount++ (but expectedModCount unchanged)
next() → modCount != expectedModCount → CME!
iterator.remove() (SAFE):
it.next() → returns element
it.remove() → modCount++ AND expectedModCount++ → in sync!
removeIf (BEST):
list.removeIf(predicate)
→ internally: iterator + remove for matching elements
→ ArrayList: optimized with bitset (single-pass shift)
Key Takeaway
The golden rule: never modify a collection inside a for-each loop. Use iterator.remove() for targeted removal or removeIf(predicate) for bulk removal. removeIf is the cleanest Java 8 solution – one line, no CME, and ArrayList's implementation is optimized with a bitset for single-pass efficiency.
For Maps, use map.entrySet().removeIf(e -> condition) to safely remove entries during iteration. For creating a filtered copy without modifying the original, use stream().filter().toList().
In the National Bank of India's end-of-day housekeeping, Rajesh cleans up closed accounts with accounts.removeIf(acc -> acc.getStatus().equals("CLOSED")) and zero-balance entries with balances.entrySet().removeIf(e -> e.getValue().compareTo(BigDecimal.ZERO) == 0). Both are safe, readable, and efficient.
Remember one sentence: for-each + remove throws CME; use removeIf for clean bulk removal or iterator.remove for targeted removal.
---