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.
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.
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
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.
Frequently Asked Questions
Do I need deepcopy for a list of numbers?
Is deepcopy the same for lists, dicts, and sets?
What is the fastest way to copy a simple dictionary?
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.
More in this topic
Nested Lists in Python
Learn how to create, access, and modify nested lists in Python. Build grids and matrices, and understand how shallow copies affect multi-dimensional data.
List Comprehensions in Python
Master Python list comprehensions to create, filter, and transform lists in a single line. Learn the syntax, common patterns, and when to use a regular loop instead.
Copy Python Lists
Learn how to copy Python lists using slicing, the copy method, the list constructor, and the copy module. Understand shallow vs deep copies.