Skip to main content

Shallow Copy vs Deep Copy in the Prototype Pattern

Python · Design Patterns · Object Cloning

Shallow Copy vs Deep Copy in the Prototype Pattern

Prototype Pattern Series

This article is part 2 of 4 in the current sequence.

Shallow copy

Creates a new outer object, but nested mutable objects can still be shared with the original.

Deep copy

Recursively duplicates nested objects too, which usually matches what beginners expect from a true clone.

Real decision

This is not about which copy function sounds better. It is about whether the clone should share nested state or not.

Introduction

The Prototype Pattern becomes much easier to misuse the moment nested data enters the picture. At the surface, cloning an object sounds simple enough. Make a copy, adjust the copy, and move on. But the real question is not whether a new top-level object was created. The real question is what happened to the objects inside it.

That is where shallow copy and deep copy stop being small Python utilities and start becoming design decisions. This article is not here to explain when Prototype helps in general. It is here to explain the technical fork inside the pattern: when your copy should share nested objects, and when it absolutely should not.

Core premise

Prototype Depends on Copying, So the Copying Rules Matter

The Prototype Pattern creates new objects by cloning existing ones. That already assumes something important: the copy should behave the way you expect after it is made. If the copy carries hidden references back into the original, the whole point of cloning can get blurry very quickly.

In Python, the copy module gives you two common choices: copy.copy() and copy.deepcopy(). They sound close. Their behavior is not.

Practical takeaway In Prototype code, the real question is not “Did I copy the object?” It is “What state is still shared after the copy?”
Shallow copy

Shallow Copy Creates a New Outer Object

A shallow copy makes a new top-level object, but it does not recursively copy the objects inside it. Nested objects are still shared.

import copy


original_list = [[1, 2, 3], [4, 5, 6]]
shallow_copied_list = copy.copy(original_list)

shallow_copied_list[0][0] = 99

print(original_list)
print(shallow_copied_list)

That behavior surprises beginners because the outer list was copied, but the inner lists were not. Both top-level lists still point to the same nested objects.

Inside Prototype

Why This Matters Inside Prototype

If your prototype contains nested lists, dictionaries, or other mutable objects, a shallow copy can quietly preserve those shared references. That means the clone may not be as independent as it looks.

import copy


class Prototype:
    def __init__(self):
        self.data = {"items": [1, 2, 3]}

    def clone(self):
        return copy.copy(self)


original = Prototype()
clone = original.clone()

clone.data["items"].append(4)

print(original.data)
print(clone.data)

In that example, both objects still share the same nested list. The clone exists, but part of its state is still tied to the original.

Deep copy

Deep Copy Recursively Separates the Nested Data

A deep copy goes farther. It creates a new top-level object and recursively copies the nested objects inside it. That gives the clone much stronger independence.

import copy


original_list = [[1, 2, 3], [4, 5, 6]]
deep_copied_list = copy.deepcopy(original_list)

deep_copied_list[0][0] = 99

print(original_list)
print(deep_copied_list)

This time, changing the copied structure does not leak into the original.

Why deep copy fits

Deep Copy Fits the Usual Prototype Expectation Better

Most beginners think “clone” means independence. Deep copy is usually the version that matches that expectation.

That is why deep copy often feels more natural in Prototype examples. If the pattern is being used to create a new object that will be customized separately, shared nested state usually works against that goal rather than helping it.

import copy


class Prototype:
    def __init__(self):
        self.data = {"items": [1, 2, 3]}

    def clone(self):
        return copy.deepcopy(self)


original = Prototype()
clone = original.clone()

clone.data["items"].append(4)

print(original.data)
print(clone.data)

Now the change stays inside the clone, which is usually what readers expect when they hear the word “prototype.”

When shallow copy still works

When Shallow Copy Can Still Be Valid

Shallow copy is not wrong. It is just more specific. It makes sense when you intentionally want some nested state to remain shared between the prototype and the copy. That can be efficient, but it also demands that the sharing be deliberate.

The danger is not shallow copy itself. The danger is accidentally using shallow copy while believing you created a fully independent clone.

Important A shallow copy is only safe when shared nested state is part of the design, not an unnoticed side effect.
The real question

The Real Choice Is About Shared Nested State

That is the cleanest way to think about it. Shallow copy means the outer object changes but nested references may still be shared. Deep copy means the nested structure is duplicated too. So the real question is not “Which copy function is better?” The real question is “Should the clone share nested state with the original or not?”

What to keep

What a Beginner Should Keep

In Prototype code, shallow copy creates a new outer shell while preserving references to inner objects. Deep copy creates a more independent clone by recursively copying those inner objects too. If you expect the clone to be safely customized without affecting the original, deep copy is usually the safer fit.

If you want shared nested state on purpose, shallow copy may be exactly right. But that choice should be intentional, not accidental.

FAQ

Frequently Asked Questions

These are the practical questions beginners usually have when shallow copy and deep copy start affecting Prototype behavior.

What is the main difference between shallow copy and deep copy?

Shallow copy creates a new outer object but can still share nested objects with the original. Deep copy duplicates the nested structure too.

Why does this matter so much in the Prototype Pattern?

Because Prototype depends on cloning. If nested state stays shared by accident, the clone may not behave like an independent new object.

Why does shallow copy surprise beginners?

Because the outer object really is new, which makes it look independent at first, even though the nested mutable objects may still be shared.

Does deep copy always mean “better”?

Not always. Deep copy is usually the safer fit when the clone should be independent, but shallow copy can be correct when shared nested state is intentional.

What is the real design question here?

The real question is whether the clone should share nested state with the original or not.

Why does deep copy often feel more natural in Prototype examples?

Because most readers hear “clone” and assume the result can be changed freely without affecting the original. Deep copy usually matches that expectation better.

When is shallow copy actually useful?

It is useful when shared nested objects are part of the design, often for efficiency or deliberate shared configuration, rather than an accident.

What is the safest beginner rule?

If the clone is supposed to be independently customizable, start by assuming deep copy is the safer choice unless you have a clear reason to share nested state.

Raell Dottin

Comments