Shallow vs Deep Copy in Python Collections

Understand shallow vs deep copy across all Python collections. Learn when slicing and the copy method are enough, and when you need the deepcopy function.

5 min read

Shallow vs deep copy in Python collections is a distinction that applies to every collection type, not just lists. Whether you are duplicating a list, a dictionary, or a set, the same question arises: do you need the copy to be completely independent at every level, or is it enough for only the outer container to be new? The answer depends on what is inside the collection. If the elements are immutable, like numbers and strings, a shallow copy is all you need. If the elements are themselves mutable collections, a shallow copy shares those inner objects with the original, and modifying them through the copy will affect the original.

The article on copying Python lists introduced shallow and deep copies in the context of lists. This article broadens the scope to cover dictionaries and sets as well, because the same principles apply uniformly. Understanding the distinction once, across all types, is more efficient than learning it separately for each collection.

Shallow copies across collection types

Every mutable collection type provides a built-in way to create a shallow copy. Lists offer the copy method, the slice syntax with omitted indices, and the list constructor. Dictionaries offer the copy method and the dict constructor. Sets offer the copy method and the set constructor. All of these create a new outer container whose elements are the same references as the original.

pythonpython
original = {"a": [1, 2], "b": [3, 4]}
shallow = original.copy()
shallow["a"].append(99)

After this code runs, original["a"] is [1, 2, 99] because the inner list is shared between the original and the shallow copy. The outer dictionaries are independent, which means adding or removing top-level keys in the copy does not affect the original. But the values that are mutable objects are shared, and changes to those objects propagate in both directions.

The same behavior occurs with sets that contain mutable elements, though sets that contain mutable elements are less common because set elements must be hashable and mutable objects typically are not. The practical impact of shallow copying is felt most acutely with dictionaries of lists, lists of dictionaries, and any nested structure where the inner containers are mutable.

When shallow copies are sufficient

A shallow copy is completely independent from its original when every element in the collection is immutable. Numbers, strings, bytes, tuples of immutable elements, and frozensets are all immutable. If your list contains only integers, your dictionary maps strings to floats, or your set holds tuples of strings, a shallow copy is as safe as a deep copy and significantly faster.

The reason is that immutable objects cannot be changed in place. Any operation that appears to modify an immutable object actually creates a new object and updates the reference. When you replace an element in a shallow copy by assigning a new value to an index or a key, you are changing the reference stored in that slot, not modifying a shared mutable object. The original collection's slot still holds the old reference, so the collections remain independent.

This is why flat lists of numbers and flat dictionaries of string-to-integer mappings are safe to shallow copy. The immutability of the elements provides the isolation that a deep copy would otherwise be needed to guarantee.

Deep copies for nested structures

When a collection contains nested mutable objects and you need true independence at every level, use the deepcopy function from the copy module. Deepcopy recursively walks through the entire structure, creating copies of every object it encounters. The result shares no mutable objects with the original, no matter how deep the nesting goes.

pythonpython
import copy
original = {"users": [{"name": "Alice"}, {"name": "Bob"}]}
deep = copy.deepcopy(original)
deep["users"][0]["name"] = "Changed"

After this code, original is unchanged. The deep copy created new outer dictionaries, new inner lists, and new innermost dictionaries at every level. This complete independence is what deepcopy guarantees and what shallow copying cannot provide.

The deepcopy function handles recursive structures safely by maintaining a memo dictionary of objects already copied during the current operation. If the same object appears multiple times in the structure, it is copied only once and the copy is reused. If the structure contains a cycle, where an object eventually contains a reference to itself, deepcopy detects the cycle and avoids infinite recursion.

The tradeoff is cost. Deepcopy is slower than a shallow copy because it traverses every nested object, and it uses more memory because every level is duplicated. Reserve deepcopy for the cases where shallow copying would cause bugs, and use shallow copies for everything else. The article on improving Python collection performance covers strategies for minimizing the need for deep copies in performance-sensitive code.

Rune AI

Rune AI

Key Insights

  • A shallow copy duplicates the outer container but shares references to the inner objects.
  • Use shallow copies (slicing, .copy(), or the constructor) for flat collections of immutable items.
  • Use copy.deepcopy() when the collection contains nested mutable objects like inner lists or dicts.
  • The same shallow vs deep distinction applies uniformly to lists, dictionaries, and sets.
  • The copy module's deepcopy function handles recursive structures and avoids infinite loops.
RunePowered by Rune AI

Frequently Asked Questions

Do I need deepcopy for a list of numbers?

No. A shallow copy using slicing, the copy method, or the list constructor is completely sufficient for a flat list of immutable items like numbers, strings, or tuples of immutable elements. Deepcopy is only necessary when the collection contains nested mutable objects like inner lists, inner dictionaries, or custom mutable objects.

Is deepcopy the same for lists, dicts, and sets?

Yes. The copy.deepcopy() function works uniformly on all collection types. It recursively copies every level of nesting, creating completely independent clones. However, you should only use it when shallow copying is insufficient, because deepcopy is slower and uses more memory.

What is the fastest way to copy a simple dictionary?

The dict copy method or passing the dict to the dict constructor both create shallow copies in a single operation. For a flat dictionary with immutable keys and values, these shallow copies are fully independent and no deep copy is needed.

Conclusion

Shallow copies are the right default for flat collections of immutable items. Deep copies are the escape hatch when nested mutable structures demand true independence at every level. Knowing which one you need prevents both unnecessary performance costs and subtle shared-state bugs.