Question 14 · Section 13

What is the difference between shallow copy and deep copy?

to other objects (collection, array, any class with reference fields) — but the elements inside remain the same (the same references).

Language versions: English Russian Ukrainian

Basic Level

Shallow Copy — only the container itself is copied — an object that contains references to other objects (collection, array, any class with reference fields) — but the elements inside remain the same (the same references).

Deep Copy — the container and all objects inside it are copied.

Example

class User {
    String name;
    User(String name) { this.name = name; }
}

List<User> original = new ArrayList<>();
original.add(new User("Ivan"));

// Shallow Copy
List<User> shallow = new ArrayList<>(original);

// Now both lists reference the same User:
shallow.get(0).name = "Petr";
System.out.println(original.get(0).name); // "Petr" — changed in the original too!

Analogy

  • Shallow Copy — making a photocopy of a box, but leaving the contents shared
  • Deep Copy — making a photocopy of the box and copying every item inside

Intermediate Level

Shallow Copy

  • Copied: primitive fields + references to objects
  • Original and copy share the same nested objects
  • Mechanisms: new ArrayList<>(original), array.clone()
  • Object.clone() — does a shallow copy by default. For deep copy you need to override clone() and manually clone nested objects.

Deep Copy

  • Copied: everything, including nested objects (recursively)
  • Original and copy are fully independent
  • Mechanisms:
    1. Copy constructors — in each class, a constructor accepting an object of the same type
    2. Serialization — via ObjectOutputStream / ObjectInputStream
    3. JSON — serialization to JSON and back
    4. Libraries — Apache Commons SerializationUtils.clone()

Deep Copy Example

public class User implements Cloneable {
    String name;
    Address address; // mutable object

    @Override
    public User clone() {
        User u = new User();
        u.name = this.name;              // String is immutable
        u.address = this.address.clone(); // deep copy
        return u;
    }
}

When to use what

Scenario Shallow Deep
Collection of String / Integer Sufficient Excessive
Collection of mutable objects Dangerous Mandatory
Complexity O(1) or O(n) for container O(n × depth) — recursively through the entire graph

Advanced Level

Shallow Immutability — a source of hidden bugs

Shallow immutability is the most common cause of bugs:

public record Config(Map<String, User> users) {
    public Config {
        users = Map.copyOf(users); // Shallow — users are the same
    }
}
// Someone does: config.users().get("admin").setRole("superadmin")

Deep Copy performance

  • O(n × m) — where n = container size, m = graph depth
  • Serialization: very slow, creates many objects
  • Copy constructors: fast, but lots of manual code

Structural Sharing

Persistent data structures (Vavr, PCollections) use structural sharing — on modification, only the path to the element is copied, and the rest of the branches are shared. This gives O(log n) instead of O(n).

Summary for Advanced

  • Shallow Copy — copies the “tip of the iceberg”; suitable for collections of immutable objects
  • Deep Copy — copies the entire structure; necessary for true immutability
  • Always clarify the depth of copying when designing APIs
  • For large graphs, consider persistent structures with structural sharing

Interview Cheat Sheet

Must know:

  • Shallow Copy — container is copied, elements remain the same (same references)
  • Deep Copy — container and all nested objects copied recursively
  • Shallow is sufficient for collections of String/Integer; Deep is mandatory for mutable objects
  • Deep Copy mechanisms: copy constructors, serialization, JSON, libraries
  • Object.clone() — shallow by default; for deep you need to override
  • Structural Sharing (Vavr) — O(log n) instead of O(n) when copying
  • Shallow Immutability — a source of hidden bugs: container is unchanged, elements are mutable

Frequent follow-up questions:

  • When is Shallow Copy dangerous? — When the collection contains mutable objects
  • Deep Copy — what mechanisms? — Copy constructors (fast), serialization (slow), JSON
  • What is Structural Sharing? — On modification, only the path is copied, the rest of the branches are shared (Vavr)
  • Does Object.clone() do deep copy? — No, shallow by default; needs to be overridden

Red flags (DO NOT say):

  • “Shallow Copy = full copy” — only the container, elements remain the same
  • “Object.clone() does deep copy by default” — no, shallow
  • “Deep Copy is always needed” — excessive for String/Integer collections
  • “Serialization is the fast way” — very slow, creates many objects

Related topics:

  • [[8. Is it enough to make all fields final for immutability]]
  • [[10. What is a defensive copy]]
  • [[12. How to protect a collection from modification]]
  • [[24. What are persistent data structures]]
  • [[29. How to properly work with collections in immutable classes]]