# Deep Dive (java.util.concurrent) - CopyOnWriteArrayList

> *"Write once, read everywhere — the list that clones itself on every mutation."*

`CopyOnWriteArrayList` is a thread-safe List that achieves concurrency by **creating a new copy of the entire underlying array on every write**. This sounds insane, but it's perfect for specific workloads.

* * *

## What is CopyOnWriteArrayList, Really?

The entire implementation can be summarized as:

*   **Reads:** Return data from the current array reference (volatile read, no lock)
    
*   **Writes:** Lock, copy the entire array, modify the copy, swap the reference
    

```java
public class CopyOnWriteArrayList<E> implements List<E> {
    
    final transient Object lock = new Object();  // Lock for writes
    private transient volatile Object[] array;    // The data — volatile!
    
    final Object[] getArray() {
        return array;
    }
    
    final void setArray(Object[] a) {
        array = a;  // Volatile write — visible to all threads
    }
}
```

![](https://cdn.hashnode.com/uploads/covers/637f189ed7d9bcd845996b4b/dbd65367-9671-43de-ac4e-da27a81a2425.png align="center")

* * *

## The Core Mechanism — Snapshot Isolation

![](https://cdn.hashnode.com/uploads/covers/637f189ed7d9bcd845996b4b/68b196fd-a252-41b8-9b23-d5eda7465c4f.png align="center")

**Key insight:** Existing readers continue using the **old array** (their snapshot). New readers see the **new array**. No locks, no ConcurrentModificationException, no coordination needed.

* * *

## How add() Works — Clone the World

```java
public boolean add(E e) {
    synchronized (lock) {          // 1. Acquire exclusive write lock
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);  // 2. Copy ENTIRE array (O(n)!)
        es[len] = e;                       // 3. Add new element to copy
        setArray(es);                      // 4. Volatile swap (publish new array)
        return true;
    }
}
```

![](https://cdn.hashnode.com/uploads/covers/637f189ed7d9bcd845996b4b/63dcc844-a5ef-4a54-ad43-1549c7d02a59.png align="center")

### set() and remove() — Same Pattern

```java
public E set(int index, E element) {
    synchronized (lock) {
        Object[] es = getArray();
        E oldValue = elementAt(es, index);
        if (oldValue != element) {
            es = es.clone();          // Full copy!
            es[index] = element;
            setArray(es);
        }
        return oldValue;
    }
}

public E remove(int index) {
    synchronized (lock) {
        Object[] es = getArray();
        E oldValue = elementAt(es, index);
        int numMoved = es.length - index - 1;
        Object[] newElements;
        if (numMoved == 0)
            newElements = Arrays.copyOf(es, es.length - 1);
        else {
            newElements = new Object[es.length - 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index + 1, newElements, index, numMoved);
        }
        setArray(newElements);
        return oldValue;
    }
}
```

**Every mutation creates a new array.** That's the "copy on write" in action.

* * *

## How get() Works — Zero Locking

```java
public E get(int index) {
    return elementAt(getArray(), index);  // That's it. No lock!
}

// getArray() is just:
final Object[] getArray() {
    return array;  // volatile read — sees the latest published version
}
```

**Time complexity: O(1)** — one volatile read + one array access. No synchronization, no CAS, no contention.

![](https://cdn.hashnode.com/uploads/covers/637f189ed7d9bcd845996b4b/ed680d82-a9c9-4a21-8c40-0eaa74082fb6.png align="center")

* * *

## How Iterators Work — Snapshot Iterators

This is the most elegant part. Iterators capture a **snapshot** of the array at creation time and are completely unaffected by subsequent modifications:

```java
public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

static final class COWIterator<E> implements ListIterator<E> {
    private final Object[] snapshot;  // Captured at creation time
    private int cursor;
    
    COWIterator(Object[] es, int initialCursor) {
        cursor = initialCursor;
        snapshot = es;  // This reference NEVER changes
    }
    
    public E next() {
        // Reads from snapshot — immune to concurrent modifications!
        return (E) snapshot[cursor++];
    }
}
```

![](https://cdn.hashnode.com/uploads/covers/637f189ed7d9bcd845996b4b/7032d2fb-1f78-4954-abe5-36c2c3768a98.png align="center")

**No ConcurrentModificationException. Ever.** The iterator works on a frozen snapshot. This is the fundamental guarantee.

> **Trade-off:** The iterator may not reflect the latest state. If you need to see real-time data, CopyOnWriteArrayList is the wrong choice.

* * *

## Write Amplification — The Cost

Every write copies the entire array. Let's quantify the pain:

![](https://cdn.hashnode.com/uploads/covers/637f189ed7d9bcd845996b4b/bed3e6d2-b588-4c85-81e7-63526b9b47b7.png align="center")

| List Size | Time per add() | Memory per add() | Verdict |
| --- | --- | --- | --- |
| 10 | ~50 ns | ~40 bytes | ✅ Fine |
| 100 | ~200 ns | ~400 bytes | ✅ Fine |
| 1,000 | ~2 μs | ~4 KB | ⚠️ Depends |
| 10,000 | ~20 μs | ~40 KB | ❌ Careful |
| 100,000 | ~200 μs | ~400 KB | ❌ Avoid |

* * *

## When to Use It

![](https://cdn.hashnode.com/uploads/covers/637f189ed7d9bcd845996b4b/7743686d-87fc-4353-938f-673c7da5aaf2.png align="center")

**Ideal use cases:**

*   **Event listener lists** — add/remove listeners infrequently, iterate (notify) often
    
*   **Configuration lists** — set once, read many times
    
*   **Caches of small reference data** — updated rarely, queried constantly
    
*   **Observer pattern** — `for (listener : listeners)` is the hot path
    

**Real-world example — Listener/Observer:**

```java
class EventBus {
    // Listeners rarely change, but are iterated on every event
    private final CopyOnWriteArrayList<EventListener> listeners =
        new CopyOnWriteArrayList<>();
    
    void addListener(EventListener listener) {
        listeners.add(listener);  // Rare write — copy is OK
    }
    
    void fireEvent(Event event) {
        // Hot path — thousands of events per second
        for (EventListener listener : listeners) {  // Snapshot iterator — no lock!
            listener.onEvent(event);
        }
    }
}
```

* * *

## CopyOnWriteArraySet

Just like Sets are Map-backed, `CopyOnWriteArraySet` is backed by `CopyOnWriteArrayList`:

```java
public class CopyOnWriteArraySet<E> extends AbstractSet<E> {
    private final CopyOnWriteArrayList<E> al;
    
    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
    
    public boolean add(E e) {
        return al.addIfAbsent(e);  // O(n) — must scan for duplicates!
    }
    
    public boolean contains(Object o) {
        return al.contains(o);     // O(n) — linear scan!
    }
}
```

> **Warning:** CopyOnWriteArraySet has O(n) `contains()` and `add()` because it uses a flat array, not a hash table. Only use it for very small sets.

* * *

## Common Pitfalls

### Pitfall 1: Using with Large Lists

```java
// ❌ TERRIBLE: Large list + frequent writes
CopyOnWriteArrayList<LogEntry> logs = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
    logs.add(new LogEntry(message));  // Copies 1M entries EACH time!
}
// Total copies: 1 + 2 + 3 + ... + 1M = ~500 billion element copies!

// ✅ Use a concurrent queue instead
ConcurrentLinkedQueue<LogEntry> logs = new ConcurrentLinkedQueue<>();
```

### Pitfall 2: Expecting Iterator to See Updates

```java
// The iterator sees a snapshot — NOT the live list
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(List.of("A", "B"));
Iterator<String> it = list.iterator();
list.add("C");
// it will NOT see "C" — it's reading from the old snapshot
```

### Pitfall 3: Iterator Doesn't Support remove()

```java
// ❌ UnsupportedOperationException!
Iterator<String> it = list.iterator();
it.next();
it.remove();  // Not supported — can't modify a snapshot!
```

* * *

## Summary

![](https://cdn.hashnode.com/uploads/covers/637f189ed7d9bcd845996b4b/75eba3f2-db4b-43da-95ab-4ae3ea461daf.png align="center")

**The one-liner:** CopyOnWriteArrayList copies the entire array on every write to achieve lock-free reads and snapshot iterators — perfect for small, rarely-modified, frequently-iterated lists like event listener registries.
