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).
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:
- Copy constructors — in each class, a constructor accepting an object of the same type
- Serialization — via
ObjectOutputStream/ObjectInputStream - JSON — serialization to JSON and back
- 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]]