Skip to content

23 · 32 · 5 min

How deep does the copy go

Copying an object that contains other objects raises a question with no universal answer: are the contents copied too?

Shallow

Fields are copied. Objects those fields point at are shared.

java
class Order {
    String id;
    List<Item> items;
}

Order copy = new Order(original.id, original.items);   // same list
copy.items.add(new Item("extra"));
// original.items now has the extra item too

Cheap, and the two objects are entangled.

Deep

Everything reachable is duplicated.

java
Order copy = new Order(original.id, new ArrayList<>(original.items));

That is one level deeper. If Item itself holds mutable state, this is still shallow with respect to the items, and truly deep copying means recursing all the way down.

Where it hurts

The classic is a defensive copy that is not deep enough:

java
public List<Item> getItems() {
    return new ArrayList<>(items);    // caller cannot add or remove
}

The caller cannot change the list, and can still call items.get(0).setPrice(0) and modify the order. A copy of a list of mutable objects protects the list and not the objects.

java
public List<Item> getItems() {
    return List.copyOf(items);        // still shares the Items
}

The only real fix is for Item to be immutable, which is why the next page follows this one.

Circular references

A deep copy of a graph with a cycle recurses forever unless it tracks what it has already copied. Serialisation based copying handles this, which is one reason people reach for it.

python
import copy
copy.copy(x)        # shallow
copy.deepcopy(x)    # deep, and handles cycles

The costs

ShallowDeep
Speedfastproportional to the whole graph
Memorysmallduplicates everything
Independentnoyes
Cyclesfineneeds tracking

Deep copying a large object graph on every request is a real performance problem, and it is usually a sign that the design is fighting mutability.

The alternative

Do not copy. If the object cannot change, sharing it is safe and free, and the whole question disappears. That is the argument for immutability, and it is why functional languages have no shallow or deep distinction to teach.