All topics
library
intermediate

Cloning: Shallow vs Deep Copy

Understand Object.clone(), its problems, and the preferred alternatives (copy constructors, factory methods).

Copying an object sounds simple until the object contains other objects. A shallow copy copies the outer object only, so both copies share whatever is inside. A deep copy copies the inner objects too, so the two are completely separate.

Shallow copy = photocopying a document with sticky notes. The photocopy looks the same, but the sticky notes (references) still point to the originals. If someone moves a sticky note, both documents are affected.

Key Concepts

1
Get this wrong and changing one copy silently changes the other.
2
Cloning is where object identity and mutability meet, and the Java mechanism for it is widely considered a design mistake — which makes "how would you copy this object?" a question about judgement rather than API recall.
3
Shallow versus deep is about what the copy shares. A shallow copy duplicates the fields; for reference fields that means both objects now point at the same nested object. Mutating that nested object through either reference is visible through both. A deep copy duplicates the nested objects too, recursively, so the two graphs are independent.
4
Object.clone() is shallow, and the mechanism around it is awkward. Cloneable is a marker interface with no methods; it does not give you clone(), it merely stops Object.clone() from throwing CloneNotSupportedException. clone() itself is protected, so you must override it to make it usable, and it bypasses constructors entirely — the object is created field by field, so any invariant your constructor enforces is not applied. final fields cannot be reassigned in clone(), which makes deep copying immutable-ish classes awkward.
Object.clone()Cloneableclone()CloneNotSupportedExceptionprotected
5
A copy constructor is the usual better answer. new Address(other) is explicit, works with final fields, runs your constructor's validation, and has no checked exception. A static factory — Address.copyOf — reads even better and can return a cached instance for immutable values. Josh Bloch's advice to prefer copy constructors over Cloneable is standard, and citing the reasoning rather than the conclusion is what lands in an interview.
new Address(other)finalAddress.copyOfCloneable
6
Serialization-based deep copy is the lazy option with real costs. Round-tripping through ObjectOutputStream, or a JSON library, produces a deep copy without writing per-class code — and is slow, requires everything to be serialisable, silently drops transient fields, and can be a security problem with untrusted input. It is defensible for a deep tree in a test fixture and hard to defend on a hot path.
ObjectOutputStreamtransient
7
The best copy is often no copy. If the object is immutable, sharing is safe and copying is waste. Making value types immutable — final fields, no setters, defensive copies of any mutable input, record where it fits — removes the whole question, which is the answer a senior candidate reaches for.
record
8
What the interviewer is probing.1. "Shallow or deep?" Probing: whether you can state the consequence. Stalls: definitions only. Moves up: what happens when the nested object is mutated through one reference — and that the bug appears far from the copy.
9
2. "How does Object.clone() behave?" Probing: mechanism. Stalls: "It copies the object." Moves up: field-by-field shallow copy, bypasses constructors, needs Cloneable to avoid an exception, protected so it must be overridden.
Object.clone()Cloneableprotected
10
3. "How would you actually copy this class?" Probing: judgement. Stalls: "Implement Cloneable." Moves up: a copy constructor or static factory, with the reasons — validation runs, final fields work, no checked exception.
final
11
4. "Deep copy a nested graph?" Probing: awareness of cycles. Stalls: "Recurse." Moves up: recursion plus an identity map to handle shared references and cycles, or serialization with its costs named.
12
5. "When do you not need to copy?" Probing: the design answer. Stalls: "Always copy to be safe." Moves up: immutable objects can be shared freely; making the type immutable removes the question entirely.
13
  What each approach gives you
14
                       clone()     copy ctor   serialize    record/
                                                            immutable
  ------------------------------------------------------------------
  Depth                shallow     you choose  deep         n/a
  Runs constructor     NO          yes         no           yes
  Works with final     no          yes         yes          yes
    fields
  Checked exception    yes         no          yes          no
  Needs Cloneable      yes         no          Serializable no
  Handles cycles       n/a         you handle  yes          n/a
  Speed                fastest     fast        slowest      free
  Drops transient      no          no          YES          n/a
15
  Shallow copy, visible failure
16
    Order a = new Order(customer, items);   // items is a List
    Order b = a.shallowCopy();
    b.items().add(newItem);
    a.items().size();   // changed too - same list instance
17
  Deep copy with shared references
18
    Use an IdentityHashMap<Object,Object> during the recursion so a
    node referenced twice is copied once, and a cycle terminates:
19
      copy(node, seen):
        if seen.containsKey(node): return seen.get(node)
        fresh = shallowCopy(node)
        seen.put(node, fresh)
        for each reference field: fresh.f = copy(node.f, seen)
        return fresh