python
dictionary
shallow-copy
deep-copy
programming

Why updating shallow copy dictionary doesn't update original dictionary?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A shallow copy of a dictionary creates a new dictionary object with references to the same values. Adding, removing, or reassigning keys in the copy does not affect the original — these operations modify the copy's structure, not the shared values. However, mutating a mutable value (like a nested list or dict) through the copy does affect the original because both dictionaries reference the same object. This is the core distinction: reassignment creates a new reference, mutation modifies the shared object.

Shallow Copy Behavior

python
1original = {"a": 1, "b": 2, "c": 3}
2copy = original.copy()
3
4# Reassigning a key in the copy does NOT affect the original
5copy["a"] = 100
6print(original)  # {"a": 1, "b": 2, "c": 3}  — unchanged
7print(copy)      # {"a": 100, "b": 2, "c": 3}
8
9# Adding a key to the copy does NOT affect the original
10copy["d"] = 4
11print(original)  # {"a": 1, "b": 2, "c": 3}  — no "d"
12print(copy)      # {"a": 100, "b": 2, "c": 3, "d": 4}
13
14# Deleting a key from the copy does NOT affect the original
15del copy["b"]
16print(original)  # {"a": 1, "b": 2, "c": 3}  — "b" still exists

When you do copy["a"] = 100, you are replacing the copy's reference for key "a" with a new object (100). The original still references the old object (1).

Where Shallow Copy Shares State

python
1original = {
2    "name": "Alice",
3    "scores": [90, 85, 95],     # Mutable list — shared by reference
4    "address": {"city": "NYC"}   # Mutable dict — shared by reference
5}
6
7copy = original.copy()
8
9# Mutating a shared mutable value DOES affect the original
10copy["scores"].append(100)
11print(original["scores"])  # [90, 85, 95, 100]  — CHANGED!
12
13copy["address"]["city"] = "LA"
14print(original["address"])  # {"city": "LA"}  — CHANGED!
15
16# But reassigning the key does NOT affect the original
17copy["scores"] = [1, 2, 3]
18print(original["scores"])  # [90, 85, 95, 100]  — unchanged
19# copy now points to a NEW list, original still points to the old one

Visualizing the References

python
1original = {"key": [1, 2, 3]}
2copy = original.copy()
3
4# Both point to the SAME list object
5print(id(original["key"]) == id(copy["key"]))  # True
6
7# After reassignment, they point to DIFFERENT objects
8copy["key"] = [4, 5, 6]
9print(id(original["key"]) == id(copy["key"]))  # False
 
1After shallow copy:
2original["key"] ──→ [1, 2, 3] ←── copy["key"]   (same object)
3
4After copy["key"] = [4, 5, 6]:
5original["key"] ──→ [1, 2, 3]
6copy["key"]     ──→ [4, 5, 6]     (new object)

Deep Copy for Full Independence

python
1import copy
2
3original = {
4    "name": "Alice",
5    "scores": [90, 85, 95],
6    "address": {"city": "NYC", "zip": "10001"}
7}
8
9# Deep copy creates independent copies of ALL nested objects
10deep = copy.deepcopy(original)
11
12deep["scores"].append(100)
13print(original["scores"])  # [90, 85, 95]  — NOT affected
14
15deep["address"]["city"] = "LA"
16print(original["address"])  # {"city": "NYC", "zip": "10001"}  — NOT affected

Ways to Create Copies

python
1original = {"a": 1, "b": [2, 3]}
2
3# Shallow copy methods (all equivalent)
4c1 = original.copy()
5c2 = dict(original)
6c3 = {**original}               # Unpacking
7
8# Deep copy
9import copy
10c4 = copy.deepcopy(original)
11
12# Verify shallow copies share nested objects
13print(c1["b"] is original["b"])  # True
14print(c2["b"] is original["b"])  # True
15print(c3["b"] is original["b"])  # True
16print(c4["b"] is original["b"])  # False  — deep copy is independent

Assignment vs Copy

python
1original = {"a": 1, "b": 2}
2
3# Assignment creates a SECOND REFERENCE to the SAME dict
4alias = original
5alias["a"] = 100
6print(original["a"])  # 100 — changed because alias IS original
7
8# Copy creates a NEW dict
9copy = original.copy()
10copy["a"] = 999
11print(original["a"])  # 100 — unchanged because copy is independent

alias = original does not copy anything — both names point to the same dictionary object. Any change through either name affects the same object.

Common Pitfalls

  • Expecting shallow copy to create fully independent nested structures: A shallow copy only copies the top-level key-value pairs. Nested mutable objects (lists, dicts, sets) are shared between the original and copy. Use copy.deepcopy() when you need full independence of nested structures.
  • Confusing reassignment with mutation: copy["key"] = new_value (reassignment) does not affect the original. copy["key"].append(item) (mutation) does affect the original because both share the same list. The distinction is whether you are changing which object a key points to (safe) or modifying the object itself (shared).
  • Using = thinking it creates a copy: new_dict = old_dict creates an alias, not a copy. Both variables reference the exact same dictionary. Any modification through either variable is visible from both. Use .copy() or dict() to create an actual independent copy.
  • Deep copying objects with circular references: copy.deepcopy() handles circular references correctly by tracking already-copied objects. However, it can be slow for large, deeply nested structures. If performance matters and you know the structure, consider manually constructing the copy.
  • Assuming {**original} is a deep copy: Dictionary unpacking ({**original}) creates a shallow copy, just like .copy(). Nested mutable values are still shared. This is a common misconception because the syntax looks like it is "rebuilding" the dictionary.

Summary

  • Shallow copy (.copy(), dict(), {**d}) creates a new dict but shares nested mutable objects
  • Adding, removing, or reassigning keys in the copy does not affect the original
  • Mutating shared mutable values (lists, dicts) through the copy does affect the original
  • Use copy.deepcopy() for fully independent copies of nested structures
  • = creates an alias (same object), not a copy — always use .copy() for an independent dictionary

Course illustration
Course illustration

All Rights Reserved.