Python shallow copy vs deep copy Explained

When you copy data in Python, the result is not always fully independent from the original.

This is especially important with nested data, such as:

  • a list inside another list
  • a dictionary containing lists
  • a list of dictionaries

The difference between shallow copy and deep copy is about what gets copied and what stays shared.

If you get this wrong, changing one object can unexpectedly change another.

Quick example

import copy

original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

original[0].append(99)

print(original)  # [[1, 2, 99], [3, 4]]
print(shallow)   # [[1, 2, 99], [3, 4]]
print(deep)      # [[1, 2], [3, 4]]

Use copy.copy() for a shallow copy and copy.deepcopy() when you need fully independent nested objects.

What this page helps you understand

  • What a copy means in Python
  • How shallow copy works
  • How deep copy works
  • Why nested objects cause confusion
  • When to use each approach

What a shallow copy does

A shallow copy creates a new outer object, but it does not fully copy the nested objects inside it.

That means:

  • the outer container is new
  • inner lists or dictionaries may still be shared
  • changing a nested mutable object can affect both versions

Example:

original = [[1, 2], [3, 4]]
copied = original.copy()

copied.append([5, 6])

print(original)  # [[1, 2], [3, 4]]
print(copied)    # [[1, 2], [3, 4], [5, 6]]

In this case, appending a new top-level item only changes copied, because the outer list is different.

But now look at a nested change:

original = [[1, 2], [3, 4]]
copied = original.copy()

copied[0].append(99)

print(original)  # [[1, 2, 99], [3, 4]]
print(copied)    # [[1, 2, 99], [3, 4]]

Both changed because original[0] and copied[0] point to the same inner list.

If you are new to this, it helps to first understand mutability in Python.

What a deep copy does

A deep copy creates a new outer object and also recursively copies the nested objects inside it.

That means:

  • the outer container is new
  • nested mutable objects are also new
  • changes inside nested structures do not affect the original

Example:

import copy

original = [[1, 2], [3, 4]]
copied = copy.deepcopy(original)

copied[0].append(99)

print(original)  # [[1, 2], [3, 4]]
print(copied)    # [[1, 2, 99], [3, 4]]

Here, the inner list was copied too, so changing copied[0] does not change original[0].

Why beginners get confused

This topic is confusing because copying often appears to work at first.

Common reasons:

  • Simple lists of numbers often behave as expected
  • Problems usually appear only with nested lists or dictionaries
  • Assignment with = does not make a copy
  • Some methods make shallow copies, not deep copies

For example, this does not create a copy:

original = [1, 2, 3]
copied = original

copied.append(4)

print(original)  # [1, 2, 3, 4]
print(copied)    # [1, 2, 3, 4]

Both names refer to the same list.

Assignment vs shallow copy vs deep copy

These three ideas are different.

Assignment

With assignment, both names point to the same object.

original = [[1, 2], [3, 4]]
assigned = original

print(original is assigned)  # True

Changing one changes the other.

Shallow copy

With a shallow copy, the outer object is new, but inner objects are shared.

original = [[1, 2], [3, 4]]
shallow = original.copy()

print(original is shallow)      # False
print(original[0] is shallow[0])  # True

Deep copy

With a deep copy, both the outer object and inner objects are copied.

import copy

original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)

print(original is deep)         # False
print(original[0] is deep[0])   # False

Common ways to make a shallow copy

These all make shallow copies:

  • list.copy()
  • dict.copy()
  • set.copy()
  • list slicing like my_list[:]
  • copy.copy() from the copy module

Examples:

numbers = [1, 2, 3]

a = numbers.copy()
b = numbers[:]

print(a)  # [1, 2, 3]
print(b)  # [1, 2, 3]

For more detail, see the list.copy() method and the dict.copy() method.

When shallow copy is enough

A shallow copy is often enough when:

  • your data is flat and not nested
  • you only need a separate outer container
  • you do not plan to change nested mutable values

Example with a flat list:

original = [1, 2, 3]
copied = original.copy()

copied.append(4)

print(original)  # [1, 2, 3]
print(copied)    # [1, 2, 3, 4]

This works fine because the list contains integers, and integers are immutable.

If you just want the common ways to duplicate a list, see how to copy a list in Python.

When deep copy is safer

A deep copy is safer when:

  • your data contains nested lists, dictionaries, or sets
  • you need to modify inner values without changing the original
  • you want a fully independent duplicate

Example with a dictionary containing a list:

import copy

original = {"scores": [10, 20]}
shallow = original.copy()
deep = copy.deepcopy(original)

original["scores"].append(30)

print(original)  # {'scores': [10, 20, 30]}
print(shallow)   # {'scores': [10, 20, 30]}
print(deep)      # {'scores': [10, 20]}

The shallow copy still shares the inner list. The deep copy does not.

Important limits and caution

Keep these points in mind:

  • Deep copy can be slower on large objects
  • Not every object copies cleanly in every situation
  • Copy only when you really need a separate object
  • Immutable values like integers and strings do not cause the same shared-mutation problem

For example, strings and integers do not behave like nested lists because they cannot be changed in place.

a = 10
b = a

b = 20

print(a)  # 10
print(b)  # 20

This is one reason mutable vs immutable types matters when learning about copying.

Common mistakes

These mistakes cause most copy-related bugs:

  • Using = and expecting a real copy
  • Using list.copy() on a nested list and expecting full independence
  • Using dict.copy() on a dictionary that contains lists or dictionaries
  • Changing an inner list after making a shallow copy
  • Not realizing the difference between mutable and immutable values

If something seems wrong, these checks can help:

print(original is copied)
print(original == copied)
print(id(original), id(copied))
print(id(original[0]), id(copied[0]))
print(type(original))

What they tell you:

  • is checks whether two names refer to the same object
  • == checks whether values are equal
  • id() shows the identity of an object
  • comparing id(original[0]) and id(copied[0]) helps detect shared inner objects
  • type() confirms what kind of object you are copying

FAQ

Does = create a copy in Python?

No. It creates another reference to the same object.

Is list.copy() a deep copy?

No. It is a shallow copy.

When do I need deepcopy()?

Use it when your object contains nested mutable values and you want full independence.

Do strings and integers need deep copy?

Usually no. They are immutable, so changing one variable does not change the original value.

Why did changing one list change the other?

They likely share the same inner object because of assignment or a shallow copy.

See also