Back to Iterator & Removing During Iteration
Q3Question 3 of 3

How do you safely remove elements during iteration?

Key Concepts
  • for-each + collection.remove() throws ConcurrentModificationException because the iterator's expectedModCount goes out of sync
  • Iterator.remove() is safe because it updates both modCount and expectedModCount simultaneously
  • removeIf(predicate) (Java 8+) is the cleanest solution; internally uses iterator.remove()
  • removeIf works on List, Set, and map.entrySet() for safe bulk removal
  • Stream filter creates a new collection instead of modifying the original
  • For Maps: map.entrySet().removeIf(e -> condition) safely removes map entries during iteration

Definition

for-each + remove throws CME; use iterator.remove() for single removals or removeIf(predicate) for bulk removal – both keep modCount in sync.
  • for-each + remove: the for-each loop uses an iterator internally; calling list.remove() increments modCount without updating the iterator's expectedModCount, causing CME on the next next() call
  • iterator.remove(): safe because it increments both modCount and expectedModCount
  • 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.

JAVA
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");
    }
}
TEXT
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.

JAVA
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);
    }
}
TEXT
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

JAVA
// 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

JAVA
// 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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate