python
programming
copy-vs-deepcopy
memory-management
data-structures

What is the difference between shallow copy, deepcopy and normal assignment operation?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

In Python, assignment (b = a) binds a new name to the same object — both variables point to identical data in memory. A shallow copy (copy.copy(a)) creates a new outer object but shares references to the nested objects inside. A deep copy (copy.deepcopy(a)) creates a fully independent clone — the new object and all its nested objects are separate from the original. Understanding these differences prevents bugs where modifying one variable unexpectedly changes another.

Normal Assignment

python
1a = [[1, 2], [3, 4]]
2b = a  # b is the SAME object as a
3
4b[0][0] = 99
5print(a)  # [[99, 2], [3, 4]] — a is modified too
6print(a is b)  # True — same object in memory
7print(id(a) == id(b))  # True

Assignment does not copy anything. a and b are two names for the same list object.

Shallow Copy

python
1import copy
2
3a = [[1, 2], [3, 4]]
4b = copy.copy(a)  # New outer list, same inner lists
5
6print(a is b)        # False — different outer lists
7print(a[0] is b[0])  # True — inner lists are shared
8
9# Modifying the outer list does NOT affect the other
10b.append([5, 6])
11print(a)  # [[1, 2], [3, 4]] — unchanged
12print(b)  # [[1, 2], [3, 4], [5, 6]]
13
14# Modifying a NESTED object affects both
15b[0][0] = 99
16print(a)  # [[99, 2], [3, 4]] — a is changed!
17print(b)  # [[99, 2], [3, 4], [5, 6]]

A shallow copy creates a new container but fills it with references to the same child objects. Changes to nested (mutable) objects are visible through both copies.

Deep Copy

python
1import copy
2
3a = [[1, 2], [3, 4]]
4b = copy.deepcopy(a)  # Completely independent copy
5
6print(a is b)        # False
7print(a[0] is b[0])  # False — different inner lists too
8
9b[0][0] = 99
10print(a)  # [[1, 2], [3, 4]] — unchanged
11print(b)  # [[99, 2], [3, 4]] — only b is modified

Deep copy recursively copies every object in the hierarchy. Modifying b at any depth does not affect a.

Alternative Shallow Copy Methods

python
1# list.copy()
2a = [1, 2, 3]
3b = a.copy()
4
5# Slice
6b = a[:]
7
8# list() constructor
9b = list(a)
10
11# dict.copy()
12d = {"x": [1, 2]}
13e = d.copy()  # Shallow — e["x"] is the same list as d["x"]
14
15# Unpacking
16b = [*a]
17e = {**d}

All of these create shallow copies — the outer container is new but nested mutable objects are shared.

Visual Comparison

python
1import copy
2
3original = {"name": "Alice", "scores": [90, 85, 92]}
4
5assigned = original
6shallow = copy.copy(original)
7deep = copy.deepcopy(original)
8
9# Modify the nested list
10original["scores"].append(100)
11
12print(assigned["scores"])  # [90, 85, 92, 100] — affected (same object)
13print(shallow["scores"])   # [90, 85, 92, 100] — affected (shared inner list)
14print(deep["scores"])      # [90, 85, 92]      — unaffected (independent copy)
15
16# Modify a top-level key
17original["name"] = "Bob"
18
19print(assigned["name"])  # "Bob"   — affected (same dict)
20print(shallow["name"])   # "Alice" — unaffected (different dict, strings are immutable)
21print(deep["name"])      # "Alice" — unaffected

When to Use Each

ScenarioUse
Both variables should always reflect the same dataAssignment (b = a)
Need a new container but nested data can be shared (flat structure)Shallow copy
Need a fully independent clone (nested mutable structures)Deep copy
Immutable data (strings, ints, tuples of immutables)Assignment (copying has no benefit)

Custom Copy Behavior

python
1import copy
2
3class Config:
4    def __init__(self, name, settings):
5        self.name = name
6        self.settings = settings  # mutable dict
7
8    def __copy__(self):
9        # Shallow copy — share settings dict
10        return Config(self.name, self.settings)
11
12    def __deepcopy__(self, memo):
13        # Deep copy — clone settings dict
14        return Config(
15            copy.deepcopy(self.name, memo),
16            copy.deepcopy(self.settings, memo)
17        )
18
19c1 = Config("app", {"debug": True})
20c2 = copy.copy(c1)
21c3 = copy.deepcopy(c1)
22
23c1.settings["debug"] = False
24print(c2.settings["debug"])  # False (shared via shallow copy)
25print(c3.settings["debug"])  # True (independent via deep copy)

Common Pitfalls

  • Assuming assignment creates a copy: b = a makes b point to the same object as a. Any mutation through b is visible through a. Use copy.copy() or copy.deepcopy() when you need an independent object.
  • Using shallow copy for nested structures: copy.copy() only copies the top-level container. If the structure contains lists, dicts, or other mutable objects, those are shared. Use copy.deepcopy() when nested mutable objects must be independent.
  • Deep copying objects with circular references: copy.deepcopy() handles circular references using a memo dictionary, but custom __deepcopy__ methods must pass memo to recursive calls. Forgetting memo can cause infinite recursion.
  • Deep copying large objects unnecessarily: deepcopy is significantly slower than shallow copy because it recursively clones every nested object. If the structure is flat (no nested mutables), shallow copy is sufficient and much faster.
  • Expecting list.copy() to deep copy: list.copy(), slicing ([:]), and dict.copy() all produce shallow copies. They do not recursively copy nested objects. Only copy.deepcopy() creates a fully independent clone.

Summary

  • Assignment (b = a) creates a new name for the same object — no copying occurs
  • Shallow copy (copy.copy(), list.copy(), [:]) creates a new outer object but shares nested references
  • Deep copy (copy.deepcopy()) recursively clones everything — fully independent
  • Use shallow copy for flat structures (lists of immutables, simple dicts)
  • Use deep copy for nested mutable structures (lists of lists, dicts of dicts)
  • Implement __copy__ and __deepcopy__ on custom classes to control copy behavior

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms