Skip to content

Python Shallow Copy vs Deep Copy

Updated 6 min read Aimerscrew

In the previous lesson, you saw that b = a doesn't copy a list; it just gives the same list a second name. So how do you make a real copy? The answer depends on what's inside your data, and that's where Python shallow copy vs deep copy comes in. In this lesson, you'll learn the three levels of "copying", when each is enough, and how to avoid the famous nested list surprise.

🌍 Real-life example

Imagine a wedding planner with a seating chart: a list of tables, and each table is a list of guests. A shallow copy is like photocopying the cover page that says "Table 1, Table 2" while both versions still point at the same table cards. A deep copy is like duplicating every table card too, so you can rearrange the copy without touching the original plan.

Assignment vs copy: what's the difference?

There are three different things you can do:

OperationNew outer object?New inner objects?
b = a (assignment)NoNo
a.copy(), a[:], list(a), copy.copy(a) (shallow copy)YesNo
copy.deepcopy(a) (deep copy)YesYes
menu = ["soup", "salad", "pie"]

same = menu          # assignment: same list
clone = menu.copy()  # shallow copy: new list

menu.append("tea")

print(same)
print(clone)
['soup', 'salad', 'pie', 'tea']
['soup', 'salad', 'pie']

same changed along with menu because it is menu. clone is a separate list, so it stayed the same.

What is a shallow copy in Python?

A shallow copy creates a new container and fills it with references to the same items the original holds. For a flat list of numbers or strings, that's all you need, because those items are immutable anyway.

Python gives you several ways to make one:

import copy

prices = [4.5, 3.0, 6.25]

a = prices.copy()       # list method
b = prices[:]           # slice of the whole list
c = list(prices)        # build a new list from it
d = copy.copy(prices)   # works for any object

print(a, b, c, d)
print(a is prices, b is prices, c is prices, d is prices)
[4.5, 3.0, 6.25] [4.5, 3.0, 6.25] [4.5, 3.0, 6.25] [4.5, 3.0, 6.25]
False False False False

All four are new lists with the same values. Dictionaries and sets have a .copy() method too, and copy.copy() from the copy module works on almost anything, including your own objects.

The nested list surprise

Things change when a list contains other mutable objects, like lists inside a list. A shallow copy only copies the outer list. The inner lists are shared:

seating = [["Ana", "Ben"], ["Cy", "Dee"]]
draft = seating.copy()

draft.append(["Eve"])        # change the outer list
draft[0].append("Finn")      # change an inner list

print(seating)
print(draft)
[['Ana', 'Ben', 'Finn'], ['Cy', 'Dee']]
[['Ana', 'Ben', 'Finn'], ['Cy', 'Dee'], ['Eve']]

Adding a new table to draft didn't affect seating, because the outer lists are separate. But adding Finn to table 1 showed up in both, because draft[0] and seating[0] are the same inner list:

seating = [["Ana", "Ben"], ["Cy", "Dee"]]
draft = seating.copy()

print(draft is seating)
print(draft[0] is seating[0])
False
True
Diagram: a shallow copy makes a new outer list whose slots still point to the same two inner lists; a deep copy makes a new outer list and new inner lists
A shallow copy shares the inner lists. A deep copy duplicates them.

What is a deep copy in Python?

A deep copy copies the container and, recursively, every mutable object inside it. The result shares nothing changeable with the original. You make one with copy.deepcopy():

import copy

seating = [["Ana", "Ben"], ["Cy", "Dee"]]
draft = copy.deepcopy(seating)

draft[0].append("Finn")

print(seating)
print(draft)
print(draft[0] is seating[0])
[['Ana', 'Ben'], ['Cy', 'Dee']]
[['Ana', 'Ben', 'Finn'], ['Cy', 'Dee']]
False

Now the original seating chart is untouched. You can rearrange draft as much as you like.

Copying dictionaries with nested data

The same rules apply to dictionaries. This is common with settings or templates, where values are often lists or other dictionaries:

import copy

template = {"title": "Invoice", "lines": ["Header", "Total"]}

shallow = template.copy()
deep = copy.deepcopy(template)

shallow["title"] = "Receipt"          # replaces a value: safe
shallow["lines"].append("Thank you")  # changes the shared list

print(template)
print(deep)
{'title': 'Invoice', 'lines': ['Header', 'Total', 'Thank you']}
{'title': 'Invoice', 'lines': ['Header', 'Total']}

Replacing shallow["title"] only rebinds a key in the new dictionary, so the template keeps "Invoice". But append() changed the list that both dictionaries share. The deep copy was completely unaffected.

🌍 Real-life example: a document template

Think of a company letter template with a header, a body and a list of attachments. Each employee who starts a new letter should get a deep copy. With a shallow copy, the first person who adds an attachment would quietly add it to everyone's letter, including the master template.

Copying your own objects

copy.copy() and copy.deepcopy() also work on objects of your own classes:

import copy

class Playlist:
    def __init__(self, name, songs):
        self.name = name
        self.songs = songs

road_trip = Playlist("Road Trip", ["Highway", "Sunset"])

shallow = copy.copy(road_trip)
deep = copy.deepcopy(road_trip)

shallow.songs.append("Exit 9")

print(road_trip.songs)
print(deep.songs)
['Highway', 'Sunset', 'Exit 9']
['Highway', 'Sunset']

When should you use a shallow or a deep copy?

  • Shallow copy when your list or dict holds only immutable things (numbers, strings, tuples of numbers), or when you want the inner objects shared. It's fast and uses little memory.
  • Deep copy when the data is nested and you plan to change the inner parts of the copy, like a game board, a seating chart or a configuration template.
  • No copy (just assignment) when you only need another name for the same object.

💡 Tip

Deep copies are slower and use more memory, because every nested object is duplicated. For big data, only deep copy when you really need independence.

Common mistakes

1. Using * to build a grid

Multiplying a list of lists repeats the same inner list, so every row is an alias:

board = [["-"] * 3] * 3
board[0][0] = "X"
print(board)
[['X', '-', '-'], ['X', '-', '-'], ['X', '-', '-']]

Build each row separately with a list comprehension instead:

board = [["-"] * 3 for _ in range(3)]
board[0][0] = "X"
print(board)
[['X', '-', '-'], ['-', '-', '-'], ['-', '-', '-']]

2. Thinking slicing makes a deep copy

grid[:] is a shallow copy, exactly like grid.copy(). Nested lists are still shared.

3. Forgetting to import copy

backup = copy.deepcopy([1, [2, 3]])
NameError: name 'copy' is not defined. Did you forget to import 'copy'?

Add import copy at the top of your file.

Practice exercise

🏋️ Try it yourself

A weekly meal plan is stored as plan = {"Mon": ["eggs"], "Tue": ["oats"]}. Make a copy called next_week, add "fruit" to Monday in next_week, and make sure the original plan does not change. Print both.

Show solution
import copy

plan = {"Mon": ["eggs"], "Tue": ["oats"]}
next_week = copy.deepcopy(plan)

next_week["Mon"].append("fruit")

print(plan)
print(next_week)
{'Mon': ['eggs'], 'Tue': ['oats']}
{'Mon': ['eggs', 'fruit'], 'Tue': ['oats']}

A shallow copy (plan.copy()) would not work here, because the Monday list would be shared.

What's next?

Now you can copy data safely. Next, discover how a for loop really walks through a list, step by step, in Python iterators.

Key takeaways

  • Assignment (b = a) never copies; both names refer to the same object.
  • A shallow copy makes a new outer container but shares the inner objects.
  • Shallow copies: list.copy(), slicing with [:], list(a), dict.copy() and copy.copy().
  • A deep copy, made with copy.deepcopy(), duplicates every nested object, so nothing is shared.
  • For flat lists of numbers or strings, a shallow copy is enough.
  • Use a deep copy for nested data you plan to change, like grids, seating charts or templates.
  • Never build a grid with [[x] * n] * n; every row would be the same list.

Frequently asked questions

What is the difference between shallow copy and deep copy in Python?

A shallow copy creates a new container but reuses the same inner objects, so changes to nested lists show up in both. A deep copy creates a new container and new copies of everything inside it, so the two are completely independent.

Is list slicing a shallow or deep copy?

Slicing with a[:] makes a shallow copy. The outer list is new, but any inner lists or other mutable objects are shared with the original.

How do you deep copy a list in Python?

Import the copy module and call copy.deepcopy(your_list). It returns a new list where every nested list and object has been copied too.

Is dict.copy() a deep copy?

No. dict.copy() is a shallow copy. If a value is a list or another dictionary, both the original and the copy share it. Use copy.deepcopy() for nested dictionaries.

When should I use a shallow copy instead of a deep copy?

Use a shallow copy when the container only holds immutable values such as numbers and strings, or when you want inner objects to be shared. It is faster and uses less memory than a deep copy.