Back to Blog
Python

Python List Copy: Shallow vs Deep Copy Explained

python list copy: Learn how to copy lists in Python correctly: the difference between assignment, shallow copy, and deep copy, and when to use each.

python listsshallow copydeep copycopy modulelist slicing
Illustration of two Python lists where one is an independent copy and the other shares nested elements, showing shallow versus deep copy behavior.

When you assign one Python list to another variable, you do not create a new list. Both names reference the same object in memory, so a change made through one name is visible through the other. To actually duplicate the data, you need an explicit copy operation. This article covers the practical options for python list copy, what each one copies, and when each is the right choice.

Assignment Creates a Reference, Not a Copy

original = [1, 2, 3] alias = original alias.append(4) print(original) # [1, 2, 3, 4]

The line alias = original binds a new name to the same list object. When append mutates the list, the change is visible through both names because there is only one list. This is the root cause of most "unexpected" list behavior in Python, and it is why copying is a separate, explicit step.

The Three Standard Ways to Copy a List

values = [1, 2, 3] copy_a = values.copy() copy_b = list(values) copy_c = values[:]

All three produce a new list object containing references to the same elements. values.copy() is the most explicit and is the method most readers will recognize first. list(values) also works with any iterable, not just lists, so it is useful when the source might be a tuple, set, or generator. values[:] is the oldest idiom and still appears in legacy code, but it is less readable than .copy().

Each of these is a shallow copy. For a flat list of immutable values like integers or strings, a shallow copy is fully independent: mutating the new list never affects the original.

Shallow Copy: What It Actually Copies

A shallow copy duplicates the outer list but not the objects stored inside it. If the list contains mutable objects, such as other lists or dictionaries, those inner objects are shared between the original and the copy.

original = [[1, 2], [3, 4]] shallow = original.copy() shallow[0].append(99) print(original) # [[1, 2, 99], [3, 4]] print(shallow) # [[1, 2, 99], [3, 4]]

The inner list [1, 2] is the same object in both original and shallow. Appending to it through shallow therefore changes what original sees. Replacing an element in shallow, such as shallow[0] = [9], would not affect original, because that rebinds a slot in the outer list rather than mutating a shared object.

Deep Copy: Full Independence with copy.deepcopy

When the list contains nested mutable structures and you need complete independence, use copy.deepcopy.

import copy original = [[1, 2], [3, 4]] deep = copy.deepcopy(original) deep[0].append(99) print(original) # [[1, 2], [3, 4]] print(deep) # [[1, 2, 99], [3, 4]]

deepcopy recursively duplicates every object reachable from the original list, so no nested structure is shared. The cost is that it must traverse the entire object graph, which makes it slower and more memory-intensive than a shallow copy. It also requires every contained object to support copying; objects that hold locks, open file handles, or other non-copyable resources may raise an error.

Performance and Memory Tradeoffs

A shallow copy is linear in the number of elements in the outer list: it allocates a new list and fills it with references to the existing elements. For a large flat list, that is cheap. A deep copy is linear in the total number of objects reachable from the list, including every nested list, dictionary, and custom object. The deeper and wider the structure, the larger the gap between the two operations.

The practical rule is to use a shallow copy unless the nested objects actually need to be independent. Copying a large structure deeply when a shallow copy would suffice wastes memory and CPU for no benefit. Conversely, relying on a shallow copy when nested objects are mutated elsewhere leads to subtle aliasing bugs that are hard to trace.

Common Mistakes and Edge Cases

One common mistake is assuming that .copy() protects against all mutation. It only protects the outer list. Any mutable element is still shared.

Tuples inside a list are shared by a shallow copy, but because tuples are immutable, this is usually harmless. The same is not true for dictionaries or lists nested inside the list.

row = {"id": 1} rows = [row] shallow = rows.copy() shallow[0]["id"] = 2 print(row) # {"id": 2}

A shallow copy of a list of custom objects shares those objects. If the custom objects are mutable and you need independent copies, copy.deepcopy is required, or the objects must implement their own copy logic.

Choosing the Right Copy Method

Use values.copy() when the list contains only immutable values and you simply want a new list you can mutate freely. Use copy.deepcopy when the list contains nested mutable structures and the copy must be fully independent. Use a shallow copy deliberately when you want the copy to share the contained objects, which is common when the objects are expensive to duplicate or when shared state is intended.

The decision is driven by what the nested objects are and whether mutations to them should propagate. For flat lists of primitives, any of the three built-in methods works; .copy() is the clearest. For nested structures, the choice between shallow and deep copy is a correctness decision, not a style preference.

python list copy: Practical Usage and Code Examples | RYUSLOG DEV