New: 6 free SQL practice datasets with 300+ questions — try the SQL Compiler →
Python

Shallow Copy vs Deep Copy in Python

Shallow copy vs deep copy in Python: what each does, how nested lists and dicts behave, and when to use copy.deepcopy, with real code and output.

Upskly AI Team September 26, 2026 7 min read
Shallow Copy vs Deep Copy in Python

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

Copy methods for a list
CodeKindInner objects
b = aNo copySame object entirely
a.copy()ShallowShared
list(a)ShallowShared
a[:]ShallowShared
[*a]ShallowShared
copy.copy(a)ShallowShared
copy.deepcopy(a)DeepCopied

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?

Choosing a copy type
SituationUse
Flat list or dict of numbers, strings, tuplesShallow copy is enough
Nested lists or dicts you will modifycopy.deepcopy()
You will only replace top-level items, never edit inner onesShallow copy
A backup or snapshot that must never changecopy.deepcopy()
Passing data to a function that must not alter itCopy 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.

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.

Open Python Compiler

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.

Try AI Assist

Test yourself

Timed questions on Mutability, Copying and Identity, with an explanation for every answer.

Take the Quiz
Upskly AI Team
Learning made simple
Scroll to Top