Back to Blog
Python

Python Copy by Reference vs Shallow Copy: When to Use Each

python copy by reference vs shallow copy: Understand how Python assignment creates references, how shallow copy duplicates the outer container, and when to use deep co...

shallow copydeep copyreference semanticscopy modulemutable objects
Diagram showing Python assignment creating a new reference to the same object, shallow copy creating a new container with shared inner objects, and deep copy creating fully independent objects.

In Python, assignment does not copy objects. When you write b = a, both names refer to the same object in memory. This is the core of the python copy by reference vs shallow copy distinction: assignment creates a new reference, while a shallow copy creates a new container that shares the same inner objects. Understanding this difference is essential for avoiding subtle bugs when working with mutable data structures like lists and dictionaries.

What Assignment Really Does

Assignment in Python binds a name to an object. It never duplicates the object itself. Consider a simple list:

a = [1, 2, 3] b = a # b is another reference to the same list b.append(4) print(a) # [1, 2, 3, 4]

Because b and a refer to the same list, mutating through one name is visible through the other. The is operator confirms they are the same object:

print(a is b) # True

This aliasing behavior is intentional. It avoids copying large data structures every time you pass them to a function or assign them to a new variable. But it also means that if you need an independent copy, assignment is not the tool.

Shallow Copy: New Container, Shared Contents

A shallow copy creates a new container object, but it fills that container with references to the same elements as the original. For lists, you can use the copy() method, slicing, or the copy module's copy() function. For dictionaries, dict.copy() works similarly.

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

The outer list is new, but the inner lists are shared. Modifying an inner list through shallow also changes original. This is the defining characteristic of a shallow copy: the top level is independent, but nested objects are still aliased.

The same applies to dictionaries with nested lists or other mutable objects.

Deep Copy: Fully Independent Objects

When you need the nested objects to be copied as well, use copy.deepcopy(). It recursively copies every object it encounters, producing a completely independent structure.

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]]

Now the two structures share nothing. Modifying deep has no effect on original. Deep copy is the right choice when you need to treat the copied structure as an isolated snapshot, especially if you plan to mutate nested objects later.

Memory and Performance Considerations

Assignment costs nothing because it only adds a name to the existing object. Shallow copy allocates memory for a new container but reuses the existing elements, so its cost is proportional to the number of top-level entries. Deep copy is the most expensive: it recursively visits every reachable object and allocates new memory for each one.

For a flat list of integers, shallow and deep copy behave identically because integers are immutable. The difference appears when the structure contains mutable objects. Deep copying a deeply nested or cyclic structure can become slow and memory-intensive. Python's deepcopy also keeps a memo dictionary to handle cyclic references, which adds overhead.

In practice, you should prefer assignment when you want aliasing, shallow copy when you need a new container but can tolerate shared mutable children, and deep copy only when full independence is required. Overusing deep copy on large structures can degrade performance unnecessarily.

Common Pitfalls with Mutable Defaults and Shared State

A frequent mistake is assuming that a shallow copy gives you full independence. For example, using list.copy() on a list of lists still shares the inner lists. Another common issue is passing a mutable object to a function and modifying it in place, which changes the caller's data unintentionally.

Consider a function that is supposed to return a copy of a configuration dictionary:

def get_config(): base = {"host": "localhost", "ports": [80, 443]} return base.copy() # shallow copy config1 = get_config() config1["ports"].append(8080) config2 = get_config() print(config2["ports"]) # [80, 443, 8080] # unexpected!

The copy() method copies the dictionary, but the ports list is still shared. The second call sees the modification made through the first call. To avoid this, use copy.deepcopy() when the structure contains mutable nested objects.

Choosing the Right Copying Strategy

The choice between assignment, shallow copy, and deep copy depends on what you intend to do with the new reference.

StrategyBehaviorMemory CostTypical Use Case
AssignmentNew name, same objectNoneAliasing, passing arguments, no copy needed
Shallow copyNew container, shared elementsO(n) for top levelNew list/dict with same mutable children, read-only or non-mutating use
Deep copyNew container, recursively new objectsO(total size)Independent snapshot, mutating nested structures

Use assignment when you want two names to refer to the same object, such as when you are building a view or a reference into a larger structure. Use shallow copy when you need a new top-level container but the elements themselves can be shared, which is common for immutable elements or when you only replace items rather than mutate them in place. Use deep copy when you need to modify nested objects without affecting the original, or when you are serializing a structure that will outlive the original.

For custom classes, you can control copy behavior by implementing __copy__ and __deepcopy__. By default, copy.copy() will create a new instance and copy the __dict__ shallowly, while copy.deepcopy() will recursively copy the __dict__. Overriding these methods lets you define exactly which attributes are shared and which are duplicated.

Understanding the distinction between reference assignment, shallow copy, and deep copy is fundamental to writing correct Python code with mutable objects. The right choice depends on whether you need independence at the top level, at every level, or not at all.

python copy by reference vs shallow copy: Practical Usage an | RYUSLOG DEV