HuntExams Academy logo
JavaIntermediate#oop#fundamentals

What is the difference between shallow copy and deep copy in Java?

Answer

A shallow copy duplicates an object's fields but for reference-type fields, copies only the reference (both objects share the same nested object). A deep copy recursively duplicates nested objects too, so the copies are fully independent.

Example

class Person implements Cloneable {
  List<String> hobbies;
  public Person clone() { 
    // shallow: shares hobbies list
    // deep would need: new ArrayList<>(hobbies)
  }
}

Detailed explanation

In Java, a shallow copy creates a new outer object and copies its field values. Primitive fields, such as int and boolean, are copied directly; reference fields receive the same references as the original. Therefore, the outer objects are different, but their referenced mutable objects are shared. A deep copy also copies the mutable objects reachable through those references, so changes to copied state do not affect the original. This distinction matters when implementing snapshots, defensive copies, or independently editable objects.

Consider a Person with a List<String> hobbies field. After a shallow copy, original != copied is true, but original.hobbies == copied.hobbies is also true. Calling copied.hobbies.add("Chess") changes the list visible through both people. However, assigning copied.hobbies = new ArrayList<>() only replaces the copied person's field; it does not replace the original's field. For this example, setting copied.hobbies = new ArrayList<>(original.hobbies) creates an independent list. Its String elements remain shared, which is safe because String is immutable. If the list contained mutable Hobby objects, those elements would also need copying for full independence.

Object.clone() performs a field-by-field shallow copy and does not invoke constructors. A class normally implements the marker interface Cloneable before calling super.clone(); otherwise, Object.clone() throws CloneNotSupportedException. Cloneable itself does not declare a clone method. A public Person.clone() implementation can call super.clone(), cast the result to Person, and then replace hobbies with a copied list, handling null appropriately. Because super.clone() declares the checked exception, the implementation must catch it or declare it. Merely implementing Cloneable does not create deep-copy behaviour.

Deep copying is often clearer through a copy constructor, factory method, or explicit copying logic rather than clone(). The implementation must define which mutable state is copied and which immutable objects may be shared. For graphs containing cycles or repeated references, an identity-based mapping from originals to copies prevents infinite recursion and preserves relationships. Interviewers typically look for correct predictions about mutation, an understanding that copying a collection does not copy its mutable elements, and awareness that deeper copies require additional time and memory.

Key points to remember

  • Shallow copies have distinct outer identities but share referenced objects.
  • Primitive field values are copied; reference field values remain references to the same objects.
  • Deep copies duplicate relevant mutable nested state; immutable objects can safely remain shared.
  • Object.clone() is shallow by default, and Cloneable is only a marker interface.
  • Copy constructors or explicit copying logic make ownership and deep-copy boundaries easier to control.

Common mistakes candidates make

  • Assuming that implementing Cloneable automatically produces a deep copy.
  • Calling new ArrayList<>(originalList) a full deep copy when its elements are mutable and still shared.
  • Confusing mutation of a shared nested object with reassignment of a reference field on one copy.

Likely follow-up questions

Is new ArrayList<>(hobbies) a shallow copy or a deep copy?

It creates a new list but shallowly copies its element references. For List<String>, sharing immutable strings is safe; for mutable elements, independent element copies are needed.

Why might a copy constructor be preferable to clone()?

A copy constructor makes copying rules explicit and avoids the Cloneable protocol and CloneNotSupportedException. It also allows normal constructor-based validation and initialisation, although it does not automatically provide deep copying.

How do you deep-copy an object graph containing cycles?

Maintain an IdentityHashMap that maps each original object to its copy, registering the copy before recursively copying its references. Reuse existing mapped copies to avoid infinite recursion and preserve shared relationships within the copied graph.

Interview tip: Explain shared references first, demonstrate adding to versus reassigning the hobbies list, and then discuss how to copy mutable nested state.

Related Interview Questions

1
JavaAdvanced#oop#interface

What is the diamond problem and how does Java avoid it with interfaces?

Open
2
JavaIntermediate#serialization#keywords

What is the purpose of the transient keyword outside of serialization context — does it affect anything else?

Open
3
JavaIntermediate#interface#java8

What is the difference between an interface's default method and a static method?

Open