skip to content

What are 'weakly consistent' iterators in the java.util.concurrent collections, and how do they differ from fail-fast iterators?

level: seniorimportance: should knowfreq 47%

answer

  1. Weakly consistent: no CME, tolerates concurrent mutation, not a snapshot
  2. Returns each element at most once; may or may not see later changes
  3. Fail-fast: modCount check -> ConcurrentModificationException, best-effort only
  4. Fail-fast fires even single-threaded (mutating during for-each)
  5. Weak consistency is what enables lock-free reads

basics

~20 s

Weakly consistent iterators (used by concurrent collections like ConcurrentLinkedQueue and ConcurrentSkipListMap) let you keep iterating even while other threads change the collection, and never throw an error. Fail-fast iterators (used by ArrayList, HashMap) throw ConcurrentModificationException if the collection changes during iteration.

solid answer

~50 s

Iterators over the java.util.concurrent collections — ConcurrentLinkedQueue, ConcurrentSkipListMap/Set, ConcurrentHashMap — are 'weakly consistent.' That means three guarantees: they tolerate concurrent modification without throwing ConcurrentModificationException; they traverse elements as they existed at or after the iterator was created; and they may (but are not guaranteed to) reflect modifications made after creation. So an element removed mid-iteration may or may not be seen, and each element is returned at most once. Crucially they are not a snapshot — they don't copy the collection. By contrast, the classic java.util collections (ArrayList, HashMap, etc.) use fail-fast iterators that track a modCount and throw ConcurrentModificationException on a best-effort basis if the structure changes during iteration, even from the same thread via the wrong API. Fail-fast is a bug-detection aid, not a concurrency guarantee. Weakly consistent iteration is what makes lock-free reads possible: you can iterate a concurrent collection without locking it and without copying it, accepting that you see a fuzzy, slightly-in-motion view rather than a frozen instant.

code

java · 13 lines
java
// Fail-fast: throws ConcurrentModificationException (even single-threaded)
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
for (Integer i : list) {
    if (i == 2) list.remove(i); // structural change via wrong API -> CME (best-effort)
}

// Weakly consistent: no CME, tolerates concurrent change, no snapshot
ConcurrentSkipListMap<Integer, String> map = new ConcurrentSkipListMap<>();
map.put(1, "a"); map.put(2, "b");
for (var e : map.entrySet()) {     // another thread may put(3,...) meanwhile
    map.put(3, "c");               // legal; loop will NOT throw
    // the entry for key 3 may or may not appear in this iteration
}

go deeper

for a junior

Knows concurrent collections don't throw ConcurrentModificationException while iterating, whereas ArrayList/HashMap do if changed mid-loop.

for a middle

Explains weakly consistent guarantees (no CME, at-or-after creation, may-or-may-not see later changes) and that fail-fast relies on a best-effort modCount check.

for a senior

Distinguishes weakly consistent vs snapshot vs fail-fast precisely, and explains that relaxing iteration semantics is what enables lock-free non-locking reads.

for a principal

Reasons about the consistency/visibility trade-offs of weak consistency for system invariants (e.g. range scans that must tolerate concurrent writers) and chooses snapshot vs weakly-consistent vs synchronized structures per correctness needs.

## The problem: iterating while others mutate Iterating a collection means walking its elements one by one. If another thread (or even the same thread through the wrong method) **modifies the collection's structure** mid-walk — adds or removes an element — the iterator's notion of "where am I" can become invalid. Different collections handle this very differently, and the two key behaviors are **fail-fast** and **weakly consistent**. ## Fail-fast iterators (classic java.util) Collections like `ArrayList`, `HashMap`, `HashSet`, `LinkedList` use **fail-fast** iterators: - Each collection keeps a **`modCount`** — an integer bumped on every structural modification. - When you create an iterator it remembers the current `modCount` (`expectedModCount`). - On each `next()`/`remove()` it checks `modCount == expectedModCount`; if they differ, it throws **`ConcurrentModificationException` (CME)**. Important subtleties: - It is **best-effort**: the check is unsynchronized, so a CME is *not guaranteed* — fail-fast is a **debugging aid to surface bugs**, never a reliable concurrency control. You must not rely on it for correctness. - It triggers even in **single-threaded** code if you mutate the collection directly during a for-each loop (instead of via `Iterator.remove()`), which is the most common way developers actually hit a CME. ## Weakly consistent iterators (java.util.concurrent) The concurrent collections — `ConcurrentLinkedQueue`, `ConcurrentSkipListMap`/`Set`, `ConcurrentHashMap`, `CopyOnWriteArrayList` differs (snapshot) — use **weakly consistent** iterators. Their contract: 1. They **never throw `ConcurrentModificationException`**, even under concurrent modification. 2. They traverse elements as they existed **at or since the iterator was constructed**. 3. They **may** reflect modifications made *after* construction, but are **not guaranteed** to — and each element is returned **at most once**. What this means in practice: you can iterate a concurrent collection from one thread while other threads add and remove elements, and the loop just keeps going. You'll see a **coherent but slightly fuzzy** view — not a frozen instant. An add or remove that happens during your walk may or may not appear. ## Weakly consistent is NOT a snapshot A common confusion: weakly consistent ≠ snapshot. - **Snapshot iterator** (e.g. `CopyOnWriteArrayList`): iterates over an immutable copy taken at creation; later changes are *never* visible, and `Iterator.remove()` is unsupported. Memory cost = a full copy on each write. - **Weakly consistent** (e.g. `ConcurrentSkipListMap`): does *not* copy; walks the live structure with lock-free reads, *may* observe later changes. Cheap, no copy, but the view can shift under you. ## Why this design enables lock-free reads The whole reason concurrent collections can offer **non-locking reads** is that they relax the iteration guarantee. A fail-fast collection would have to lock (or copy) to iterate safely under concurrency. By promising only weak consistency, the concurrent collections let a reader traverse the live structure with no lock and no `ConcurrentModificationException`, which is exactly the scalability you want. The trade-off — you accept an approximate, in-motion view and `size()` that's an estimate — is usually fine for the concurrent use cases these collections target. ## Quick mental model - **Fail-fast** = "the world must hold still while I look; if it moved, I'll yell (CME) — usually." - **Weakly consistent** = "I'll keep looking even as the world moves; I won't yell, but I can't promise I saw the latest change." - **Snapshot** = "I photographed the world at the start and only look at the photo."

  • Is a weakly consistent iterator the same as a snapshot (copy-on-write) iterator?
    No. A snapshot iterator (CopyOnWriteArrayList) walks an immutable copy made at creation and never sees later changes, and Iterator.remove() is unsupported. A weakly consistent iterator walks the live structure with no copy and MAY observe later changes — it's cheaper but offers a fuzzier, in-motion view.
  • Can a single-threaded program throw ConcurrentModificationException?
    Yes. Fail-fast iterators throw it whenever the collection is structurally modified during iteration through anything other than the iterator's own remove() — e.g. calling list.remove() inside a for-each loop — even with one thread.

saying these in an interview costs you the question

  • Treating fail-fast CME as a reliable concurrency guarantee instead of a debugging aid
  • Calling weakly consistent iterators a 'snapshot' (that's CopyOnWriteArrayList)
  • Expecting a weakly consistent iterator to definitely see every concurrent change
  • Assuming concurrent collections must be locked to iterate safely
  • Thinking ConcurrentModificationException only happens across threads

context