What is CopyOnWriteArrayList?
4. Iterator = snapshot, not live view 5. Memory spike on write → OOM risk 6. Stale data — acceptable for your scenario? 7. Alternatives: ConcurrentLinkedQueue, synchronizedList
🟢 Junior Level
CopyOnWriteArrayList is a thread-safe list that copies the array on every write.
Simple analogy: A photograph. Readers look at a photo (the old version), while the author takes a new photograph (a copy with changes).
List<String> list = new CopyOnWriteArrayList<>();
// Read: fast, no locks.
// volatile Object[] array → get() = simple read from the array, no atomic operations or synchronized.
String s = list.get(0);
// Write: creates a copy of the array
list.add("A"); // Copy → add → replace
When to use:
- Many reads, few writes
- Listener lists
🟡 Middle Level
How it works
// Internally: volatile Object[] array
// Read (get):
return array[index]; // No locks!
// Write (add):
1. Lock
2. newArray = Arrays.copyOf(array, size + 1)
3. newArray[size] = element
4. array = newArray // volatile write
5. Unlock
Iterator = Snapshot
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
Iterator<String> it = list.iterator(); // Snapshot!
list.add("B"); // Modified the list
it.next(); // → "A" (iterator does NOT see "B"!)
// → Fail-Safe, never throws an Exception
// it.remove() → UnsupportedOperationException!
When to use
// ✅ Listener lists
List<Listener> listeners = new CopyOnWriteArrayList<>();
// Reads (notifications) >>> writes (subscribing)
for (Listener l : listeners) { // Fast!
l.onEvent();
}
// ✅ Rarely changing caches
When NOT to use
// ❌ Frequent writes
list.add(e); // Copies the array every time!
// → O(n) per write
// → Temporary memory doubling
// ✅ Alternatives:
ConcurrentLinkedQueue<String> queue;
Collections.synchronizedList(list);
**Key difference:** synchronizedList blocks every read, COWAL does not. synchronizedList iterator throws CME on modification, COWAL — no (snapshot).
🔴 Senior Level
Memory Footprint
// On write:
oldArray: 1 million elements → 4 MB
newArray: 1 million + 1 → 4 MB
// → 8 MB temporarily!
// For large lists → OOM risk
// → GC pressure on frequent writes
Happens-Before guarantee
// volatile array = Happens-Before
// Write in one thread → visible to all in others
// But: window between copying and writing
// → Readers may see "stale" data
Stale Data problem
- Fail-Safe = iterator never throws ConcurrentModificationException (works on a copy)
- Stale Data = “outdated data” — reader sees the array version from the time the iterator was created
- Eventual Consistency = data will become current not instantly, but after the next write
// Thread 1: reads array (old version)
// Thread 2: copies, modifies, writes new array
// Thread 1: still reads the old array
→ Consistency is eventual, not strong!
Iterator limitations
// Fail-Safe:
// → Never ConcurrentModificationException
// → But: doesn't see changes after creation
// Iterator methods:
it.remove() // → UnsupportedOperationException!
it.add() // → UnsupportedOperationException!
it.set() // → UnsupportedOperationException!
Production Experience
Real scenario: Listener list
// Event Bus: 1000 subscribers
// Notifications: 100,000/sec
// Subscribe/unsubscribe: 1/sec
// Ratio of 100,000 reads to 1 write → array copying (O(n)) happens 1 time per 100,000 operations → overhead is negligible.
CopyOnWriteArrayList:
→ Read: O(1), lock-free
→ Write: O(n), but rare!
→ Perfectly suited!
Best Practices
- Primarily for read-heavy scenarios. Acceptable for small collections (< 100) even with moderate writes.
- Listener lists — ideal use case
- Avoid with frequent writes
- Iterator = snapshot, not live view
- Memory spike on write → OOM risk
- Stale data — acceptable for your scenario?
- Alternatives: ConcurrentLinkedQueue, synchronizedList
Summary for Senior
- Copy-On-Write = array copy on write
- Read = O(1), lock-free, volatile array
- Write = O(n), copy + replace
- Iterator = snapshot, fail-safe
- Memory = temporary doubling on write
- Listeners = ideal use case
- Stale data = eventual consistency
- Avoid for write-heavy scenarios
🎯 Interview Cheat Sheet
Must know:
- CopyOnWriteArrayList — on every write (add/set/remove), creates a copy of the internal array, reads are lock-free through volatile
- Read = O(1), no locks. Write = O(n), array copy + replace + lock
- Iterator = snapshot at creation time, fail-safe (does not throw CME), does not see changes after creation
- Iterator does NOT support remove/add/set — UnsupportedOperationException
- Ideal use case: listener lists — read-to-write ratio of 100,000:1
- Memory: temporarily doubles memory on write (oldArray + newArray) — OOM risk for large lists
- Happens-Before guarantee through volatile array, but eventual consistency — readers may see stale data
- Alternatives for write-heavy: ConcurrentLinkedQueue, Collections.synchronizedList
Frequent follow-up questions:
- Why doesn’t the iterator see added elements? — The iterator works on a snapshot (a copy of the array at creation time). New elements are added to a new array — the snapshot doesn’t see them.
- How does COWAL differ from synchronizedList? — synchronizedList blocks EVERY read. COWAL does not block reads. synchronizedList iterator throws CME on modification, COWAL — no (snapshot).
- When is COWAL a bad choice? — With frequent writes: every write = copying the entire array. For 1 million elements = 4 MB copy + temporary memory doubling.
- What is eventual consistency in COWAL? — A thread reads the old array, another thread writes a new one. The reader will see new data not instantly, but only after the next reference to the array.
Red flags (DO NOT say):
- ❌ “COWAL blocks reads” — no, reads are fully lock-free through volatile
- ❌ “COWAL iterator shows live data” — it’s a snapshot, doesn’t see changes after creation
- ❌ “COWAL is suitable for frequent writes” — O(n) per write + memory doubling, use ConcurrentLinkedQueue instead
- ❌ “COWAL iterator supports remove()” — no, UnsupportedOperationException for all modifications
Related topics:
- [[18. What is ConcurrentHashMap]]
- [[19. How does ConcurrentHashMap ensure thread-safety]]
- [[14. What is Map and what implementations exist]]