Question

Copying Dictionaries in Python Without Changing the Original

pythondictionaryreference

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 = dict1 puts 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.

Take Quiz

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["settings"]["theme"] = "dark"

print(user["settings"]["theme"])       # light
print(user_copy["settings"]["theme"])  # dark

deepcopy() recursively copies nested mutable objects.

Step by Step Execution

Consider the original assignment:

dict1 = {"key1": "value1", "key2": "value2"}
dict2 = dict1
dict2["key2"] = "WHY?!"
  1. Python creates one dictionary object and dict1 refers to it.
  2. dict2 = dict1 reads the reference stored in dict1 and stores that same reference in dict2.
  3. There is still only one dictionary object.
  4. dict2["key2"] = "WHY?!" changes the value in that shared dictionary.
  5. Looking up dict1 shows the changed value because dict1 refers 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["key2"] = "updated"

print(dict1 is dict2)  # False
print(dict1)           # {'key1': 'value1', 'key2': 'value2'}
print(dict2)           # {'key1': 'value1', 'key2': 'updated'}

Here, .copy() makes a second outer dictionary, so changing a top-level key in dict2 does not affect dict1.

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'}
Take Quiz

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

Use deepcopy() deliberately. It can be unnecessary and more expensive for large or complex data. If only one nested section needs changing, copying only that section is often clearer:

updated_config = config.copy()
updated_config["logging"] = config["logging"].copy()
updated_config["logging"]["level"] = "DEBUG"

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.

a = {"x": 1}
b = {"x": 1}

print(a is b)  # False: different objects
print(a == b)  # True: same contents

Use == for contents and is only when identity matters.

Deep-copying everything by default

deepcopy() is useful for nested mutable data, but it may be more work than necessary. For flat dictionaries, prefer .copy(). For a single nested part, copy only the part you plan to modify.

Comparisons

OperationCreates a new outer dictionary?Nested lists/dictionaries independent?Best use
dict2 = dict1NoNoIntentionally share one dictionary
dict1.copy()YesNoEdit top-level keys independently
dict(dict1)YesNoEquivalent shallow-copy alternative
{**dict1}YesNoCopy while combining dictionaries
deepcopy(dict1)YesUsually yesEdit nested mutable data independently

Copying versus updating

original = {"name": "Kai", "active": True}
updated = {**original, "active": False}

This creates a shallow copy and changes active in the new dictionary. It is concise when you want to copy and override top-level keys in one expression.

original = {"name": "Kai", "active": True}
original.update({"active": False})

update() changes original itself. Use it only when mutation is intended.

Take Quiz

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 is dict2)

It returns True when both variables refer to the same object.

Take Quiz

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

  1. Create an original profile dictionary with a nested preferences dictionary.
  2. Write a function named set_theme that accepts a profile and a theme.
  3. Return a new profile instead of mutating the profile argument.
  4. Update only preferences["theme"] in the returned profile.
  5. Demonstrate that the original profile still has its original theme.
Take Quiz

Keep learning

Related questions

View more