You make a copy of a friend's "Road Trip" playlist, rename it "Gym Mix" and add a few songs. Next day your friend asks why their road trip playlist suddenly has workout songs in it. Your "copy" was not really a separate playlist: the song list inside it was still shared. That is the difference between a shallow copy and a deep copy, and it causes some of the most confusing bugs in object-oriented code.
Shallow copy = a new outer object, but everything inside it is shared with the original.
Deep copy = a new outer object AND new copies of everything it owns, so the two are fully independent.
Real-world analogy: Sharing a Google Docs link with a friend is a shallow copy: you each have your own link, but both open the same document, so an edit by either of you shows up for both. File > Make a copy is a deep copy: your friend gets a separate document, and nothing they change touches yours.
Reference copy, shallow copy and deep copy of a Playlist that holds Song objects
1. Three Ways to "Copy" an Object
When people say "copy", they can mean three very different things:
| New outer object? | New inner list? | New inner objects? | Changes visible in both? | |
|---|---|---|---|---|
Reference copy (b = a) | No | No | No | Everything |
| Shallow copy | Yes | No | No | Changes to shared inner data |
| Deep copy | Yes | Yes | Yes | Nothing |
The difference only matters when an object holds other mutable objects: lists, arrays, maps or other class objects. If a class holds only numbers and strings, a shallow copy and a deep copy behave the same.
2. Reference Copy: Not a Copy at All
In Java, Python and JavaScript, a variable that holds an object really holds a reference (an address) to it. Assigning it to another variable copies the reference, not the object. Both names now point to the same playlist.
Output
roadTrip: My Mix [Kesariya, Naatu Naatu, Jai Ho]
mine: My Mix [Kesariya, Naatu Naatu, Jai Ho]
Same object? true
Renaming mine renamed roadTrip, because there is only one object. C++ is different: Playlist mine = roadTrip; calls the copy constructor and creates a real copy. To get a second name for the same object in C++, you use a reference (Playlist&) or a pointer.
3. Shallow Copy: New Box, Same Contents
A shallow copy creates a new outer object and copies each field into it. For a number or a string, that copies the value. For a list or another object, it copies only the reference, so the inner data ends up shared.
Output
original: Road Trip -> Kesariya:5, Naatu Naatu:0, Believer:0
copy: Gym Mix -> Kesariya:5, Naatu Naatu:0, Believer:0
Same object? false
Same song list? true
After a shallow copy: two Playlist objects, one shared song list
Look at what happened to each kind of field:
namechanged only in the copy. Assigningcopy.name = "Gym Mix"replaces the copy's own field; the original keeps "Road Trip".- The new song appeared in both.
copy.songsandoriginal.songsare the same list, so adding to one adds to "both". - The rating changed in both. The
Songobjects inside the list are shared too.
Most built-in copy tools are shallow:
- Java:
Object.clone()(default),new ArrayList<>(list),List.copyOf(),Arrays.copyOf(). A copied list is a new list, but it holds the same element objects. - Python:
copy.copy(obj),list(items),items[:],dict.copy(). - JavaScript: spread
{...obj}and[...arr],Object.assign(),Array.from(),arr.slice(). - C++: the compiler-generated copy constructor copies each member. That is a deep copy for value members such as
std::stringandstd::vector<Song>, but a shallow copy for pointer members, which is why the example stores its list behind ashared_ptr.
4. Deep Copy: Everything Is Independent
A deep copy creates a new outer object, a new list, and a new copy of every object inside the list. After that, nothing is shared: any change to the copy stays in the copy.
Output
original: Road Trip -> Kesariya:0, Naatu Naatu:0
copy: Gym Mix -> Kesariya:5, Naatu Naatu:0, Believer:0
Same song list? false
Same first song? false
After a deep copy: each playlist has its own list and its own Song objects
How each language does it:
- Java: write a copy constructor (
new Playlist(other)) that creates a new list and copies each element. This is clearer and safer than overridingclone(). - C++: write your own copy constructor and copy assignment operator that allocate new storage (the Rule of Three: if you need a custom destructor, copy constructor or copy assignment, you almost always need all three). Better still, store members by value (
std::vector<Song>) and the default copy is already deep. - Python:
copy.deepcopy(obj)walks the whole object graph and copies everything, and it handles cycles too. - JavaScript:
structuredClone(obj)deep-copies plain data, but the result is a plain object without your class methods. For class instances, write adeepCopy()method. (JSON.parse(JSON.stringify(obj))also loses methods, dates andundefinedvalues.)
5. The C++ Pointer Trap
In C++, a shallow copy is not just confusing, it can crash your program. If a class owns memory through a raw pointer and you do not write a copy constructor, the default one copies the pointer. Both objects then point to the same memory, and both destructors try to free it.
class Buffer {
int* data;
int size;
public:
Buffer(int n) : data(new int[n]), size(n) {}
~Buffer() { delete[] data; }
// No copy constructor: the compiler copies the pointer (shallow)
};
Buffer a(10);
Buffer b = a; // b.data == a.data
// end of scope: b frees data, then a frees the SAME data again -> undefined behaviour
Fix it with a deep-copying copy constructor and assignment operator, or better, avoid raw owning pointers: use std::vector, std::string or std::unique_ptr, which already copy (or refuse to copy) correctly. For a class that should never be copied, such as a database connection, delete the copy operations: Connection(const Connection&) = delete;
6. Practical Example: Question Paper Sets (Prototype Pattern)
An exam department prepares "Set A" of a question paper, then needs "Set B" with a few changes. Building Set B from scratch would repeat all the work. Instead, Set B is created by copying Set A and editing the copy. Creating new objects by copying an existing one is the Prototype design pattern, and it only works if you choose the right kind of copy for each field.
QuestionPaper owns its questions (deep copy) and refers to a shared ExamHall
Output
Set A @ Hall A, JNTU Hyderabad (total 17)
Q1. Define a class [2]
Q2. Explain inheritance with an example [5]
Q3. Design a parking lot [10]
Set B @ Hall A, JNTU Hyderabad (total 17)
Q1. Define a class [2]
Q2. Explain polymorphism with an example [5]
Q3. Design a parking lot [8]
Q4. What is an immutable object? [2]
Same hall object? true
Same question list? false
Why This Design Works
- Owned data is deep-copied. The questions belong to the paper. Editing Q2 and Q3 in Set B did not touch Set A, and Set A still totals 17 marks.
- Shared data is shared on purpose. Both sets are written in the same hall, so
copyAs()passes the sameExamHallobject. Copying it would create a second "Hall A" that could drift out of sync with the real one. - Immutable fields need no copying. Strings such as the question text cannot change, so sharing them is safe.
- Python note:
copy.deepcopy()would copy the hall as well. A hand-writtencopy_as()(or a custom__deepcopy__) lets you decide field by field.
7. Which Copy Should You Use?
A quick check for choosing between a shallow and a deep copy
| Shallow copy | Deep copy | |
|---|---|---|
| Speed and memory | Fast, copies only the top level | Slower, copies the whole object graph |
| Independence | Inner objects are shared | Fully independent |
| Good for | Objects that hold only primitives, strings or immutable objects; data that is meant to be shared | Objects that own mutable lists or objects; prototypes, snapshots, undo history, test fixtures |
| Risk | Unexpected changes through shared data | Performance cost, and copying things that should be shared (connections, caches) |
If the inner objects are immutable, you get the best of both: a shallow copy is safe because the shared parts can never change. This is one more reason to prefer immutable value objects (see the Immutability article).
8. Common Mistakes
- Thinking
b = amakes a copy in Java, Python or JavaScript. It only creates a second name for the same object. - Assuming a copied list copies its elements.
new ArrayList<>(list),list[:]and[...arr]create a new list of the same objects. - Using
{...obj}on nested data and then changing a nested field. Only the top level was copied. - Relying on Java's
clone()without overriding it to copy mutable fields. The defaultclone()is shallow. - Writing a C++ class with a raw owning pointer and no copy constructor, copy assignment and destructor (Rule of Three).
- Deep-copying shared resources such as a database connection, a logger or a cache. Some things should stay shared.
Summary
- Reference copy (
b = a): one object with two names. - Shallow copy: a new outer object whose inner lists and objects are shared with the original.
- Deep copy: a new outer object plus new copies of everything it owns; the two are fully independent.
- Most built-in copy tools (
clone(),copy.copy(), spread,Object.assign()) are shallow. For deep copies use copy constructors,copy.deepcopy(),structuredClone()or your owndeepCopy()method. - In LLD, deep-copy what an object owns, share what is meant to be shared, and make inner objects immutable whenever you can.