skip to content

What is the difference between a shallow and a deep copy when cloning in Java, and how do you implement a deep clone?

level: middleimportance: must knowfreq 68%

answer

  1. Shallow copies fields; deep copies what fields point to
  2. Primitives independent; references shared in shallow copy
  3. super.clone() then reassign mutable fields
  4. final fields can't be reassigned in clone() (a flaw)
  5. Cycles + nested objects make deep clone hard

basics

~20 s

A shallow copy duplicates the object's fields but shares the objects those fields point to. A deep copy also duplicates the referenced objects so the copy is fully independent. Object.clone() gives a shallow copy; you must add deep copying yourself.

solid answer

~40 s

Object.clone() performs a shallow copy: it copies each field's value, so primitive fields are independent but reference fields still point to the *same* underlying objects. If you mutate a shared list or array through one copy, the other sees the change — usually a bug. A deep copy clones the referenced objects too, recursively, so the two instances are independent. To deep-clone you override clone(), call super.clone() to get the shallow copy, then replace each mutable reference field with a copy of its contents (e.g. clone the internal array, deep-clone nested objects). Note that final fields can't be reassigned this way, which is one of clone()'s structural problems. Common alternatives are copy constructors or serialization-based deep copy.

code

java · 18 lines
java
class Cart implements Cloneable {
    List<String> items = new ArrayList<>();

    // SHALLOW (buggy): both carts share one list
    // public Cart clone() { return (Cart) super.clone(); }

    // DEEP: clone the mutable field too
    @Override
    public Cart clone() {
        try {
            Cart copy = (Cart) super.clone();   // shallow: items reference shared
            copy.items = new ArrayList<>(this.items); // make it independent
            return copy;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }
}

go deeper

for a junior

Can define shallow vs deep copy and state that clone() is shallow by default.

for a middle

Implements a correct deep clone by reassigning mutable fields after super.clone(), and recognizes array/nested-object pitfalls.

for a senior

Reasons about final-field conflicts, cyclic graphs, and inheritance cooperation; chooses copy constructors when those bite.

for a principal

Sets team conventions (immutable value types to avoid copying, copy constructors over clone) and weighs serialization deep copy versus hand-written copy for large/cyclic graphs and performance.

## Two kinds of copy When you copy an object, the question is: do you copy only the object itself, or also everything it *refers to*? - **Shallow copy:** a new object is allocated and each field's *value* is copied. For a **primitive** field (`int`, `boolean`, etc.) the value *is* the data, so it's independent. For a **reference** field (a pointer to another object — a `List`, an array, a nested object), the *reference* is copied, meaning both the original and the copy point at the **same** referenced object. They share it. - **Deep copy:** the new object also gets fresh copies of every object it refers to, recursively. The two graphs of objects are completely independent — mutating one never affects the other. ## Why shallow copies cause bugs Consider an object holding a mutable `List`: ```java class Cart implements Cloneable { List<String> items = new ArrayList<>(); } ``` A shallow clone copies the `items` *reference*, so both carts share one list. Adding an item to the clone also appears in the original. This silent aliasing is the classic shallow-copy bug. ## Implementing a deep clone The recipe: take the shallow copy from `super.clone()`, then **reassign each mutable reference field to a fresh copy**: ```java @Override public Cart clone() { try { Cart copy = (Cart) super.clone(); copy.items = new ArrayList<>(this.items); // independent list return copy; } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } ``` For nested mutable objects you must clone them too (and *their* mutable fields), recursing all the way down. Arrays have their own `clone()` that you should call (`arr.clone()`), but for an array of mutable objects that array-clone is itself shallow, so you then clone each element. ## The hard edges - **`final` fields:** you can't reassign a `final` field inside `clone()` (assignment after `super.clone()` is forbidden for `final`), so deep-cloning is incompatible with making mutable-reference fields `final`. This is a well-known flaw of the `clone()` approach. - **Cyclic graphs:** naive recursion on object graphs that contain cycles will loop forever; you'd need an identity map of already-copied objects. - **Inheritance:** every class in the hierarchy must cooperate, or `super.clone()` returns the wrong runtime type / misses fields. ## Alternatives that sidestep all this - **Copy constructor:** `new Cart(otherCart)` — you control the copy depth explicitly and can use `final` fields. - **Serialization deep copy:** serialize then deserialize the object graph; correct for arbitrary graphs but slow and requires `Serializable`. - **Libraries / records:** value-like types (records with immutable fields) often need no copying at all because they can be shared safely. ## Key terms - **Aliasing:** two references pointing at the same object, so a change via one is visible via the other. - **Object graph:** the network of objects reachable from a root via its reference fields. - **Recursive copy:** copying an object, then copying each object it references, then theirs, and so on.

  • Why can't deep cloning with clone() work well with final reference fields?
    After super.clone() you must reassign each mutable reference field to a fresh copy, but a final field cannot be reassigned. So clone()-based deep copy forces those fields to be non-final, weakening immutability — a key reason copy constructors are preferred.
  • How would serialization produce a deep copy, and what are its downsides?
    Serialize the object to a byte stream then deserialize it; the reconstructed graph is fully independent and handles cycles. Downsides: requires Serializable, is much slower, can break on transient/non-serializable fields, and has historical security concerns.

A shallow copy is like photocopying a folder's cover but sharing the same papers inside; a deep copy photocopies every page too, so the two folders are fully separate.

saying these in an interview costs you the question

  • Believing Object.clone() recursively copies referenced objects
  • Calling arr.clone() and assuming it deep-copies an array of mutable objects
  • Making mutable reference fields final while relying on clone() to deep-copy them
  • Ignoring cycles when hand-rolling a recursive deep copy

context