Question 20 · Section 4

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

Language versions: English Russian Ukrainian

🟢 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

  1. Primarily for read-heavy scenarios. Acceptable for small collections (< 100) even with moderate writes.
  2. Listener lists — ideal use case
  3. Avoid with frequent writes
  4. Iterator = snapshot, not live view
  5. Memory spike on write → OOM risk
  6. Stale data — acceptable for your scenario?
  7. 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]]