CopyOnWriteArrayList — When and Why
Fast recall
Quick revision
Core answer
Knowledge of concurrent collections, copy-on-write semantics, and trade-offs between read-heavy and write-heavy access patterns
Using CopyOnWriteArrayList in a write-heavy scenario where it causes severe GC pressure, or assuming its iterator reflects concurrent modifications
Contrast with synchronized(list) and Collections.synchronizedList — those block readers during writes. CopyOnWriteArrayList allows lock-free reads but has O(n) write cost. Also mention it is ideal for listener/observer lists where reads vastly outnumber registrations.
Interview-ready guide
Interview answer
CopyOnWriteArrayList has a deliberately extreme design: every write copies the entire array — making reads completely lock-free.
Write behavior:
add(),set(),remove()each allocate a new array, copy everything, apply the change, swap the volatile reference- O(n) per write with GC pressure from discarded arrays — expensive on writes
Read behavior:
- Array contents never change after creation — any thread can read or iterate without synchronization
- Iterators take a snapshot at creation time and never throw
ConcurrentModificationException
vs Collections.synchronizedList():
- Synchronized list locks on every operation including reads — one reader blocks all others
- CopyOnWriteArrayList allows fully concurrent reads — dramatically faster when read thousands of times per second
Sweet spot:
- Read-heavy, write-rare: event listener lists iterated on every event but modified only at startup; configuration holders checked on every request but updated rarely
I recommend it specifically for observer/listener patterns. For anything write-heavy, the array-copying overhead makes it the wrong choice.
CopyOnWriteArrayList — When and Why
Direct Answer
CopyOnWriteArrayList creates a full copy of the backing array on every write, making reads completely lock-free. It is ideal for read-heavy, write-rare scenarios like listener registries. Avoid it when writes are frequent — each write is O(n) and causes GC pressure.
Deep Dive & Architecture
compute, merge, computeIfAbsent, and computeIfPresent are Map methods (Java 8+) that atomically combine get + check + put into a single operation, replacing the verbose get-check-put anti-pattern. computeIfAbsent(key, mappingFunction) — if key absent, compute value and put; most-used for lazy initialization of multi-valued maps: map.computeIfAbsent(key, k -> new ArrayList<>()).add(value). computeIfPresent(key, remappingFunction) — if key present, recompute value (or remove if null returned). compute(key, remappingFunction) — recompute regardless of presence (function gets old value or null). merge(key, value, remappingFunction) — if absent, put value; if present, apply function to combine: map.merge(word, 1, Integer::sum) is the idiomatic word-counter. Why they matter: (1) atomic in ConcurrentHashMap — no lost updates from concurrent get-check-put races; (2) less code than if (map.containsKey(k)) map.put(k, ...) else map.put(k, ...); (3) cleaner intent — the operation says what it does. Pitfalls: (1) not atomic in non-concurrent maps (HashMap) — only the API is single-call, not thread-safe; (2) returning null from a remapping function removes the key — surprising; (3) don't recursively modify the map inside the function — undefined behavior; (4) merge doesn't handle null values the same way as compute — read the Javadoc. For interviews: lead with 'replaces get-check-put, atomic in ConcurrentHashMap, idiomatic for multimap lazy-init and counters'.
CopyOnWriteArrayList creates a full copy of the backing array on every write, making reads completely lock-free. It is ideal for read-heavy, write-rare scenarios like listener registries. Avoid it when writes are frequent — each write is O(n) and causes GC pressure.
A common mistake is confusing the interface with the implementation (e.g., saying List when you mean ArrayList), or not knowing that Map does not extend Collection.
Common Mistakes & Pitfalls
Using CopyOnWriteArrayList in a write-heavy scenario where it causes severe GC pressure, or assuming its iterator reflects concurrent modifications
Check your understanding
Answer these without looking back at the article.
- What is the Java Collections Framework (JCF) and why does it exist? It provides a unified architecture of interfaces and implementations for storing and manipulating groups of objects, replacing the ad-hoc pre-JCF classes (Vector, Hashtable, Enumeration) with a consistent, interoperable API.
- How does HashSet work internally in Java?
- How does HashMap internally handle hash collisions in Java 8+? Explain the transition from linked list to tree.
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.