A shallow copy makes a new outer container but reuses the same inner objects. A deep copy copies the container and everything inside it, all the way down. The difference only matters when your data is nested and contains mutable objects such as lists or dictionaries. Use copy.copy() (or .copy()) for a shallow copy and copy.deepcopy() for a deep one.
This guide shows exactly what each one does, with real output, and how to decide which you need.
In this guide
The short version
b = a– no copy at all. Two names, one object.copy.copy(a),a.copy(),list(a),a[:]– shallow: new outer list, same inner objects.copy.deepcopy(a)– deep: new outer list and new inner objects.- If everything inside is immutable (numbers, strings, tuples of those), a shallow copy is enough.
Assignment is not a copy
In Python, b = a does not copy anything. It attaches a second name to the same object (see Mutable vs Immutable Objects in Python). To get an independent object you have to ask for a copy:
a = [1, 2, 3]
b = a
c = a.copy()
a.append(4)
print(a, b, c)
Output
[1, 2, 3, 4] [1, 2, 3, 4] [1, 2, 3]
a and b both show the new item, because they are one list. c is a separate list, so it did not change.
Shallow copy: one level deep
A shallow copy builds a new outer container, then fills it with references to the same inner objects as the original. For a list of numbers that is all you need. For a list of lists, watch what happens:
import copy
orig = [[1, 2], [3, 4]]
shallow = copy.copy(orig)
shallow.append([5, 6])
print(orig)
shallow[0].append(99)
print(orig)
print(orig[0] is shallow[0])
Output
[[1, 2], [3, 4]]
[[1, 2, 99], [3, 4]]
True
Appending a new row to the copy did not touch the original, because the outer lists are separate. But appending to the first inner list showed up in both, because orig[0] and shallow[0] are the very same list. The last line, True, confirms it.
When the items are immutable, sharing them is harmless, since nobody can change them in place. Replacing an item in the copy only changes the copy:
a = [1, "text", (2, 3)]
b = a.copy()
b[0] = 99
print(a, b)
Output
[1, 'text', (2, 3)] [99, 'text', (2, 3)]
Deep copy: everything is copied
copy.deepcopy() walks through the whole structure and copies every nested object, so the result shares nothing mutable with the original:
import copy
orig = [[1, 2], [3, 4]]
deep = copy.deepcopy(orig)
deep[0].append(99)
print(orig)
print(deep)
print(orig[0] is deep[0])
Output
[[1, 2], [3, 4]]
[[1, 2, 99], [3, 4]]
False
Now the original stayed clean, and orig[0] is deep[0] is False: the inner lists are different objects.
Deep copy is also smart about loops. If an object contains itself, it copies the structure once and keeps the loop intact instead of running forever:
import copy
a = [1]
a.append(a)
b = copy.deepcopy(a)
print(b[1] is b, b[1] is a)
Output
True False
Ways to copy, and which kind they are
| Code | Kind | Inner objects |
|---|---|---|
| b = a | No copy | Same object entirely |
| a.copy() | Shallow | Shared |
| list(a) | Shallow | Shared |
| a[:] | Shallow | Shared |
| [*a] | Shallow | Shared |
| copy.copy(a) | Shallow | Shared |
| copy.deepcopy(a) | Deep | Copied |
Checking the four shallow methods: each makes a new list but shares the inner items
True
True
Dictionaries and sets work the same way: d.copy() is shallow, and copy.deepcopy(d) is deep.
Realistic examples: dictionaries, records and objects
A dictionary that holds a list
Copying a settings dictionary and then changing a list inside it is a classic way to corrupt the original:
import copy
config = {"name": "app", "tags": ["a", "b"]}
s = config.copy()
d = copy.deepcopy(config)
config["tags"].append("c")
print(s["tags"])
print(d["tags"])
Output
['a', 'b', 'c']
['a', 'b']
A list of records
Data work often produces a list of dictionaries. Taking a “backup” with .copy() protects nothing once you edit a row:
rows = [{"id": 1, "tags": []}, {"id": 2, "tags": []}]
backup = rows.copy()
rows[0]["tags"].append("x")
print(backup[0]["tags"])
Output
['x']
The backup’s first row changed, because backup[0] and rows[0] are the same dictionary. Use copy.deepcopy(rows) for a real backup.
Your own objects
The same rules apply to instances of your own classes. A shallow copy copies the object but shares its attributes:
import copy
class Team:
def __init__(self, members):
self.members = members
t1 = Team(["Asha", "Ravi"])
t2 = copy.copy(t1)
t3 = copy.deepcopy(t1)
t1.members.append("Meera")
print(t2.members)
print(t3.members)
Output
['Asha', 'Ravi', 'Meera']
['Asha', 'Ravi']
The shallow copy t2 shares the members list with t1. The deep copy t3 has its own.
Which one should I use?
| Situation | Use |
|---|---|
| Flat list or dict of numbers, strings, tuples | Shallow copy is enough |
| Nested lists or dicts you will modify | copy.deepcopy() |
| You will only replace top-level items, never edit inner ones | Shallow copy |
| A backup or snapshot that must never change | copy.deepcopy() |
| Passing data to a function that must not alter it | Copy first, deep if nested |
Deep copy does more work, and on big structures it can be slow and memory-hungry, so do not reach for it by reflex. Use it when nested mutable data is really being modified. Some objects, such as open files, cannot be deep-copied at all.
Common mistakes
Mistake 1: assuming .copy() protects nested data
It only protects the top level. Nested lists and dictionaries are still shared.
Mistake 2: using deepcopy everywhere
It is safe but costly. For a flat list, a.copy() is faster and clearer.
Mistake 3: mixing up a copy with a new reference
If you see two lists change together, you probably have one list with two names. The is operator will tell you: a is b is True only when they are the same object.
Try it yourself
Work out each answer first, then open the solution.
1. What does this print?
a = [1, [2, 3]]
b = a.copy()
b[1].append(4)
print(a)
Show answer
Output
[1, [2, 3, 4]]A shallow copy shares the inner list, so appending to b[1] also changes a[1].
2. Same code, but with copy.deepcopy(a). What does a look like now?
Show answer
import copy
a = [1, [2, 3]]
b = copy.deepcopy(a)
b[1].append(4)
print(a)
Output
[1, [2, 3]]The deep copy has its own inner list, so the original is untouched.
3. What does this print?
a = [1, 2, 3]
b = a[:]
b.append(4)
print(a)
Show answer
Output
[1, 2, 3]a[:] makes a shallow copy of a flat list, which is enough here because the items are numbers.
4. Does a deep copy of a tuple that contains a list share that list?
Show answer
import copy
t = (1, [2])
u = copy.deepcopy(t)
u[1].append(3)
print(t, u)
Output
(1, [2]) (1, [2, 3])The tuple is immutable but the list inside it is not, and deepcopy copied it, so changing u left t alone.
Try these in our free Python compiler.
Frequently asked questions
What is the difference between shallow copy and deep copy in Python?
A shallow copy creates a new container but keeps references to the same inner objects. A deep copy creates a new container and new copies of everything inside it, recursively.
Is b = a a copy in Python?
No. It makes a second name for the same object. Changes made through either name are visible through both.
How do I copy a list in Python?
For a flat list use a.copy(), list(a) or a[:]. For a list that contains other lists or dictionaries, use copy.deepcopy(a).
Does .copy() on a dictionary make a deep copy?
No, it is shallow. The keys and values are the same objects as in the original, so a list stored as a value is shared between both dictionaries.
When do I need deepcopy?
When your data is nested with mutable objects inside, and you are going to change those inner objects in the copy or the original, and the other one must stay unchanged.
Is deepcopy slow?
It does much more work than a shallow copy, since it visits every nested object, so on large structures it can be noticeably slower. Use it when you need it, not by default.
Related reading
- Mutable vs Immutable Objects in Python – why sharing objects causes these bugs.
- Python List vs Tuple vs Set: Key Differences – the collections being copied.
- Python Dictionaries Explained With Examples – copying and nesting dictionaries.
- 7 Python Gotchas That Trip Up Interviews – more traps like this one.
Run this code in your browser
The free Upskly compiler runs Python with nothing to install. Paste the example, change it and see what happens.
Stuck on a traceback?
AI Assist works inside the Python notebook, so you can ask about an error or a concept without leaving the cell.
Test yourself
Timed questions on Mutability, Copying and Identity, with an explanation for every answer.