Question
Given this Python code, why does changing dict2 also change dict1, and how can you create a copy that can be edited without modifying the original dictionary?
dict1 = {"key1": "value1", "key2": "value2"}
dict2 = dict1
dict2["key2"] = "WHY?!"
print(dict1)
# {'key1': 'value1', 'key2': 'WHY?!'}
Short Answer
You will learn that dict2 = dict1 does not copy a dictionary: it gives two variable names access to the same dictionary object. You will also learn when to use a shallow copy such as .copy() and when nested data requires copy.deepcopy().
Concept
In Python, a variable does not contain a dictionary itself. Instead, it refers to an object stored elsewhere in memory.
When you write:
dict2 = dict1
Python does not create a second dictionary. It makes dict2 refer to the exact same dictionary as dict1. Therefore, changing the dictionary through either name changes the shared object.
dict1 = {"status": "pending"}
dict2 = dict1
dict2["status"] = "done"
print(dict1["status"]) # done
To edit a separate top-level dictionary, create a shallow copy:
dict2 = dict1.copy()
A shallow copy creates a new outer dictionary, but values inside it may still be shared when those values are mutable objects such as lists or other dictionaries. For completely independent nested data, use copy.deepcopy().
Mental Model
Think of a dictionary as a filing cabinet and a variable as a label pointing to that cabinet.
dict2 = dict1puts a second label on the same filing cabinet. Editing files through either label changes the same cabinet.dict2 = dict1.copy()creates a new cabinet and copies the top-level files into it.deepcopy()creates a new cabinet and also creates new copies of cabinets, folders, and lists stored inside it.
Two variable names do not necessarily mean two separate objects.
Syntax and Examples
Use one of these approaches depending on the dictionary's contents.
Assignment: no copy
dict2 = dict1
Both variables reference one dictionary. This is useful when you intentionally want a shared object.
Shallow copy: new outer dictionary
dict1 = {"key1": "value1", "key2": "value2"}
dict2 = dict1.copy()
dict2["key2"] = "updated value"
print(dict1) # {'key1': 'value1', 'key2': 'value2'}
print(dict2) # {'key1': 'value1', 'key2': 'updated value'}
Other shallow-copy syntax:
dict2 = dict(dict1)
dict2 = {**dict1}
dict2 = dict1 | {} # Python 3.9+
For most code, dict1.copy() is the clearest option.
Deep copy: independent nested objects
from copy import deepcopy
user = {
"name": "Ava",
"settings": {"theme": "light"}
}
user_copy = deepcopy(user)
user_copy[][] =
(user[][])
(user_copy[][])
Step by Step Execution
Consider the original assignment:
dict1 = {"key1": "value1", "key2": "value2"}
dict2 = dict1
dict2["key2"] = "WHY?!"
- Python creates one dictionary object and
dict1refers to it. dict2 = dict1reads the reference stored indict1and stores that same reference indict2.- There is still only one dictionary object.
dict2["key2"] = "WHY?!"changes the value in that shared dictionary.- Looking up
dict1shows the changed value becausedict1refers to the same object.
You can verify shared identity with is:
print(dict1 is dict2) # True
Now use a shallow copy:
dict1 = {"key1": "value1", "key2": "value2"}
dict2 = dict1.copy()
dict2[] =
(dict1 dict2)
(dict1)
(dict2)
Real World Use Cases
Dictionary copies are useful whenever code needs to derive changed data while preserving an original value.
- API request options: Copy default headers or options, then add a request-specific value.
- Application configuration: Start from default settings and create user-specific settings without changing defaults.
- Data transformation: Keep the original record while producing a cleaned or enriched version.
- State updates: Create a new state dictionary before changing fields, making updates easier to reason about.
- Test data: Copy fixture data before each test so one test does not mutate data used by another test.
Example: build request headers without changing shared defaults.
default_headers = {
"Accept": "application/json",
"User-Agent": "MyApp/1.0"
}
headers = default_headers.copy()
headers["Authorization"] = "Bearer token-value"
print(default_headers)
# {'Accept': 'application/json', 'User-Agent': 'MyApp/1.0'}
Real Codebase Usage
Developers often copy dictionaries at boundaries where data must not be unexpectedly mutated.
Apply defaults safely
def create_settings(overrides):
defaults = {"theme": "light", "notifications": True}
settings = defaults.copy()
settings.update(overrides)
return settings
This avoids modifying defaults when callers provide overrides.
Return derived data without mutating input
def mark_complete(task):
updated_task = task.copy()
updated_task["completed"] = True
return updated_task
The caller can keep both the original task and the updated task.
Copy nested configuration when it will be edited
from copy import deepcopy
def enable_debug(config):
updated_config = deepcopy(config)
updated_config["logging"]["level"] = "DEBUG"
return updated_config
Common Mistakes
Assuming assignment creates a copy
This code shares one dictionary:
original = {"role": "viewer"}
copy = original
copy["role"] = "admin"
print(original["role"]) # admin
Use original.copy() when you need a separate outer dictionary.
Forgetting that .copy() is shallow
original = {"tags": ["python", "api"]}
copy = original.copy()
copy["tags"].append("testing")
print(original["tags"])
# ['python', 'api', 'testing']
The outer dictionaries differ, but both contain a reference to the same list. Use deepcopy() or copy the nested list:
copy = original.copy()
copy["tags"] = original["tags"].copy()
Using is to compare dictionary contents
is checks whether two variables refer to the same object. It does not compare values.
Comparisons
| Operation | Creates a new outer dictionary? | Nested lists/dictionaries independent? | Best use |
|---|---|---|---|
dict2 = dict1 | No | No | Intentionally share one dictionary |
dict1.copy() | Yes | No | Edit top-level keys independently |
dict(dict1) | Yes | No | Equivalent shallow-copy alternative |
{**dict1} | Yes | No | Copy while combining dictionaries |
deepcopy(dict1) | Yes | Usually yes |
Cheat Sheet
# No copy: both names point to one object
alias = original
# Recommended shallow copy
copy = original.copy()
# Other shallow copies
copy = dict(original)
copy = {**original}
copy = original | {} # Python 3.9+
# Copy and override top-level values
updated = {**original, "status": "done"}
# Deep copy nested mutable values
from copy import deepcopy
copy = deepcopy(original)
# Identity versus content
first is second # Same object?
first == second # Same key/value contents?
- Use
.copy()for a flat dictionary or when only top-level keys will change. - A shallow copy still shares nested mutable objects.
- Use
deepcopy()when nested dictionaries, lists, or sets must also be independent. - Assignment (
=) creates an alias, not a dictionary copy.
FAQ
Does dict2 = dict1 copy a dictionary in Python?
No. It creates another reference to the same dictionary object.
What is the simplest way to copy a Python dictionary?
Use dict2 = dict1.copy(). It creates a shallow copy.
Why does changing a nested list affect the original after .copy()?
.copy() only copies the outer dictionary. The nested list is still the same shared list object in both dictionaries.
When should I use copy.deepcopy()?
Use it when you need to modify nested mutable values, such as dictionaries or lists inside a dictionary, without affecting the original.
Is {**dict1} a dictionary copy?
Yes. It creates a new outer dictionary, so it is a shallow copy like dict1.copy().
Does copying a dictionary duplicate strings and integers?
A shallow copy creates a new dictionary container but reuses its values. This is generally safe for immutable values such as strings, integers, and tuples containing only immutable values.
How can I check whether two variables share the same dictionary?
Use is:
print(dict1 dict2)
Mini Project
Description
Create a small profile-update function for an application. It should return an edited version of a user profile without changing the stored original profile, including its nested notification preferences.
Goal
Safely update a nested profile setting while preserving the original dictionary.
Requirements
- Create an original profile dictionary with a nested
preferencesdictionary. - Write a function named
set_themethat accepts a profile and a theme. - Return a new profile instead of mutating the profile argument.
- Update only
preferences["theme"]in the returned profile. - Demonstrate that the original profile still has its original theme.
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Add Rows to a Pandas DataFrame in Python
Learn how to add rows to a Pandas DataFrame, why repeated row appends are slow, and when to use loc, concat, or record lists.
Call a Function by Name in a Python Module
Learn how to call a function by name in a Python module using strings, getattr, and safe patterns for dynamic function dispatch.