Back to Blog
Python

Python Shallow vs Deep Copy: Key Differences

python shallow vs deep copy: Understand the difference between shallow and deep copy in Python, when to use each, and how to avoid common pitfalls with mutable objects.

shallow copydeep copyPython copy moduleobject referencesmutable objects
Diagram showing shallow copy sharing nested objects while deep copy creates independent copies.

Python Shallow vs Deep Copy: Key Differences

When you copy an object in Python, you might expect a fully independent duplicate. In practice, Python offers two copying mechanisms: shallow copy and deep copy. The difference determines whether nested objects are shared or duplicated. This distinction matters for mutable data structures like lists, dictionaries, and custom objects.

How Python Handles Object References

Before copying, it's important to understand that Python variables hold references to objects, not the objects themselves. Assigning one variable to another does not copy the object; it creates a new reference to the same object. For example:

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

The alias and original point to the same list, so modifying one affects the other. To create a true copy, you need the copy module or other copying techniques.

Shallow Copy: Sharing Nested Objects

A shallow copy creates a new container object but populates it with references to the same elements as the original. For a list, you can use copy.copy() or the slice operator [:]:

import copy original = [1, 2, [3, 4]] shallow = copy.copy(original) shallow.append(5) # Does not affect original shallow[2].append(6) # Affects original's nested list print(original) # [1, 2, [3, 4, 6]] print(shallow) # [1, 2, [3, 4, 6], 5]

The top-level list is independent, but the nested list is shared. If you modify a nested element, the change appears in both objects. This behavior is efficient because it avoids copying potentially large nested structures.

Deep Copy: Fully Independent Duplicates

A deep copy recursively copies all objects, including nested ones. Use copy.deepcopy():

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

Now the nested list is also duplicated, so changes to deep do not affect original. Deep copy handles arbitrary nesting and works with custom objects, but it can be expensive for large or complex structures.

When to Use Shallow vs Deep Copy

The choice depends on whether you need to isolate nested mutations. Use shallow copy when:

  • You only need to modify the top-level structure.
  • The nested objects are immutable (e.g., integers, strings, tuples).
  • You want to avoid the overhead of deep copying.

Use deep copy when:

  • You must ensure that nested mutable objects are independent.
  • You are working with complex data structures that will be modified at multiple levels.
  • You need to snapshot a state without affecting the original.

A common mistake is assuming that slicing a list creates a deep copy. It does not; it creates a shallow copy. For nested lists, you need deepcopy.

Common Pitfalls and Edge Cases

Shallow copy behavior can surprise developers when nested objects are modified. For example, copying a list of dictionaries with copy.copy() shares the dictionaries. To avoid this, use deepcopy or explicitly copy each element.

Custom objects can control copying by defining __copy__ and __deepcopy__ methods. Without these, copy.deepcopy uses default behavior, which may not be correct for objects with external resources like file handles or database connections. In such cases, you should implement these methods to define what "deep" means for your object.

Immutable objects like tuples and frozensets are always shared in copies because they cannot be modified. This is safe and efficient.

Performance and Memory Considerations

Deep copy is more expensive because it recursively traverses the entire object graph. For large data structures, this can be a significant cost in both time and memory. Shallow copy is O(1) for the top-level container, but it shares nested objects, which may lead to unintended side effects.

If you frequently need independent copies, consider designing your data structures to minimize nesting or use immutable data types. For example, using tuples instead of lists for nested elements can make shallow copy sufficient.

Practical Example: Copying Configuration Data

Suppose you have a configuration dictionary with nested settings. You need to pass it to a function that modifies it without affecting the original. A deep copy is appropriate:

import copy config = { "server": {"host": "localhost", "port": 8080}, "features": ["auth", "logging"] } def apply_defaults(cfg): cfg["features"].append("metrics") return cfg safe_config = copy.deepcopy(config) apply_defaults(safe_config) print(config["features"]) # ['auth', 'logging'] print(safe_config["features"]) # ['auth', 'logging', 'metrics']

If you only needed to add a top-level key, a shallow copy would suffice. Understanding the level of independence you require is the key to choosing correctly.

python shallow vs deep copy: Practical Usage and Code Exampl | RYUSLOG DEV