How to copy a dictionary in Python
In this guide, you'll learn how to copy a dictionary in Python, including different methods, tips, and how to debug common errors.

You will often need to copy a dictionary in Python. Understand the distinction between a shallow and a deep copy to prevent unexpected data changes in your code.
Here, you'll explore methods like copy() and deepcopy(). You will find practical tips, see real-world applications, and get debugging advice to help you select the best approach for your needs.
Using the copy() method
original = {'name': 'John', 'age': 30, 'city': 'New York'}
copied = original.copy()
print(copied)
print(original is copied) # Check if they are the same object--OUTPUT--{'name': 'John', 'age': 30, 'city': 'New York'}
False
The built-in copy() method creates a shallow copy of a dictionary. It constructs a new dictionary object and then populates it with references to the items contained in the original. This is a quick and efficient way to duplicate a dictionary's structure and top-level items.
The expression original is copied evaluates to False because they are two distinct objects in memory, even though their contents are identical. This separation is key—it allows you to modify the copied dictionary without accidentally changing the original, preventing unexpected side effects in your code.
Basic dictionary copying methods
Besides the copy() method, you can also create shallow copies using the dict() constructor, dictionary comprehensions, or the dedicated copy module.
Using the dict() constructor
original = {'name': 'John', 'age': 30, 'city': 'New York'}
copied = dict(original)
print(copied)
original['country'] = 'USA' # Modify original
print(f"Original: {original}, Copy: {copied}")--OUTPUT--{'name': 'John', 'age': 30, 'city': 'New York'}
Original: {'name': 'John', 'age': 30, 'city': 'New York', 'country': 'USA'}, Copy: {'name': 'John', 'age': 30, 'city': 'New York'}
You can also create a shallow copy by passing your dictionary to the dict() constructor. This approach builds an entirely new dictionary object and then populates it with the key-value pairs from the original.
- Notice how adding the
'country'key tooriginaldoesn't change thecopieddictionary. - This demonstrates that they are separate objects. It’s a concise and highly readable alternative to using the
copy()method for creating a shallow copy.
Using dictionary comprehension
original = {'name': 'John', 'age': 30, 'city': 'New York'}
copied = {key: value for key, value in original.items()}
print(copied)
original.clear() # Empty the original
print(f"Original: {original}, Copy: {copied}")--OUTPUT--{'name': 'John', 'age': 30, 'city': 'New York'}
Original: {}, Copy: {'name': 'John', 'age': 30, 'city': 'New York'}
Dictionary comprehension offers a flexible, Pythonic way to create a shallow copy. This method iterates through each key-value pair in the original dictionary and uses them to build an entirely new dictionary object.
- It’s a highly readable one-liner that achieves the same result as the
dict()constructor or thecopy()method. - As the example shows, when you call
original.clear(), thecopieddictionary remains intact. This is because it’s a separate object in memory, not just a reference to the original.
Using the copy module
import copy
original = {'name': 'John', 'age': 30, 'city': 'New York'}
copied = copy.copy(original)
print(copied)
print(id(original) == id(copied)) # Compare memory addresses--OUTPUT--{'name': 'John', 'age': 30, 'city': 'New York'}
False
Python's copy module offers a dedicated function, copy.copy(), for creating shallow copies. While it works much like the other methods you've seen, it's part of a standard library built specifically for all types of copying operations.
- The code explicitly proves the two dictionaries are separate objects by comparing their memory addresses using the
id()function. - Because
id(original) == id(copied)evaluates toFalse, you have direct confirmation that modifying the top level of one won't impact the other.
Advanced dictionary copying techniques
When shallow copies don't cut it for nested data, you can use advanced techniques like deepcopy(), the ** operator, or even the json module.
Deep copying nested dictionaries with deepcopy()
import copy
nested = {'name': 'John', 'info': {'age': 30, 'city': 'New York'}}
shallow = nested.copy()
deep = copy.deepcopy(nested)
nested['info']['age'] = 31 # Modify nested value
print(f"Shallow: {shallow}\nDeep: {deep}")--OUTPUT--Shallow: {'name': 'John', 'info': {'age': 31, 'city': 'New York'}}
Deep: {'name': 'John', 'info': {'age': 30, 'city': 'New York'}}
When your dictionary contains other mutable objects, like lists or other dictionaries, a shallow copy isn't enough. The copy.deepcopy() function recursively duplicates everything. This means it creates new, independent copies of all nested objects, not just references to them.
- Notice how changing the
agein thenesteddictionary also changes it in theshallowcopy. This happens because both dictionaries point to the very sameinfodictionary. - The
deepcopy, however, remains unchanged. It has its own separateinfodictionary, completely protecting it from modifications to the original.
Using the ** unpacking operator
original = {'name': 'John', 'age': 30}
copied = {**original}
print(copied)
# Merge with another dictionary during copy
other = {'city': 'New York', 'country': 'USA'}
merged = {**original, **other}
print(merged)--OUTPUT--{'name': 'John', 'age': 30}
{'name': 'John', 'age': 30, 'city': 'New York', 'country': 'USA'}
The ** operator offers a modern, highly readable way to create a shallow copy. When you write {**original}, you're essentially unpacking the key-value pairs from original directly into a new dictionary literal. It's a concise and elegant syntax that achieves the same result as other shallow copy methods.
- The real power of this operator shines when you need to merge dictionaries.
- You can combine multiple dictionaries in one go, like with
{**original, **other}, to create a new dictionary containing the elements of both.
Using json module for serializable dictionaries
import json
complex_dict = {'name': 'John', 'scores': [95, 89, 78], 'info': {'city': 'New York'}}
json_copied = json.loads(json.dumps(complex_dict))
complex_dict['info']['city'] = 'Boston'
print(f"Original: {complex_dict}\nJSON copy: {json_copied}")--OUTPUT--Original: {'name': 'John', 'scores': [95, 89, 78], 'info': {'city': 'Boston'}}
JSON copy: {'name': 'John', 'scores': [95, 89, 78], 'info': {'city': 'New York'}}
You can achieve a deep copy by serializing a dictionary to a JSON string with json.dumps() and then parsing it back into a new object with json.loads(). This clever technique creates a completely independent duplicate of your data, similar to a deep copy.
- This two-step process effectively severs all ties to the original object, including any nested structures.
- As the example shows, changing the
cityin the original doesn't impact the copy because it's a completely independent object. - Just remember, this method only works if your dictionary contains JSON-serializable data types.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. You describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
For the dictionary copying techniques we've explored, Replit Agent can turn them into production-ready tools. You can use it to:
- Build a configuration management tool that generates environment-specific settings from a master template, using shallow copies for simple overrides.
- Create a user profile editor that lets you draft changes, using
deepcopy()to create a temporary state that can be saved or discarded without affecting the live profile. - Deploy a game state simulator that tests different moves by creating independent copies of the game board, ensuring each scenario is isolated.
Bring your concept to life by describing it to Replit Agent. It will handle the coding, testing, and deployment for you, right in your browser.
Common errors and challenges
Even simple dictionary copies can lead to tricky bugs, but you can avoid them by understanding a few common challenges.
Avoiding mutable default arguments when copying dictionaries
A classic pitfall in Python is using a mutable object, like a dictionary, as a default argument in a function. The default object is created only once when the function is defined, not each time it's called. If your function modifies this dictionary, the changes will persist across subsequent calls, leading to unexpected behavior.
- Instead of setting a default argument to
{}, set it toNone. - Inside the function, check if the argument is
Noneand, if so, initialize a new empty dictionary. This ensures every function call works with a fresh, independent object.
Handling nested objects when using copy()
It's easy to forget that the copy() method performs a shallow copy. This becomes a problem when your dictionary contains other mutable objects, such as lists or other dictionaries. Since the shallow copy only duplicates references to these nested objects, modifying them in the copied dictionary will also alter them in the original.
This happens because both the original and the copy are pointing to the exact same nested object in memory. If you need to ensure complete independence between the original and the copy—including all nested elements—you must use copy.deepcopy() instead.
Troubleshooting KeyError when accessing copied dictionaries
A KeyError occurs when you try to access a dictionary key that doesn't exist. This can happen with a copied dictionary if the original was modified after the copy was made, or if you simply try to access a key that was never there to begin with. Blindly accessing keys can make your code fragile.
- To handle this gracefully, use the
get()method. It returns the value for a key if it exists, orNone(or a default value you specify) if it doesn't, preventing a crash. - Alternatively, you can check for a key's existence before accessing it using the
inkeyword, likeif 'my_key' in copied_dict:.
Avoiding mutable default arguments when copying dictionaries
Using a mutable default argument, like an empty dictionary, is a classic Python pitfall. The default object is created only once, so modifications made in one function call will unexpectedly carry over to the next, creating hard-to-debug side effects.
The code below shows this in action. Watch how settings from the first call to add_user_settings() unexpectedly appear in the second call, creating a shared state where none was intended.
def add_user_settings(user_id, settings={}):
settings['user_id'] = user_id
return settings
user1 = add_user_settings(1)
user1['theme'] = 'dark'
print(f"User 1: {user1}")
user2 = add_user_settings(2)
print(f"User 2: {user2}") # Contains user1's theme unexpectedly
The settings dictionary is created only once and shared across all calls to add_user_settings. Because of this, modifications made for user1 persist and unintentionally leak into the settings for user2. The corrected implementation below shows how to prevent this shared state.
def add_user_settings(user_id, settings=None):
if settings is None:
settings = {}
settings_copy = settings.copy()
settings_copy['user_id'] = user_id
return settings_copy
user1 = add_user_settings(1)
user1['theme'] = 'dark'
print(f"User 1: {user1}")
user2 = add_user_settings(2)
print(f"User 2: {user2}") # Correctly has no theme from user1
The corrected add_user_settings function fixes the bug by setting the default argument to None instead of an empty dictionary. This ensures a fresh dictionary is created for each function call that doesn't provide one.
- The key is to check if the argument is
Noneand initialize a new dictionary inside the function.
This prevents changes from one call from leaking into another. Keep an eye out for this when defining functions with mutable defaults.
Handling nested objects when using copy()
Using copy() on dictionaries with nested objects can cause tricky bugs. Since it only creates a shallow copy, the nested objects inside the original and the copy are actually the same. Modifying one will unintentionally change the other.
The following code shows this in action. Notice how changing the theme in the copied dictionary also alters the original, because the nested preferences dictionary is shared between them.
original = {'user': 'John', 'preferences': {'theme': 'light', 'font_size': 12}}
copied = original.copy()
# Modify nested preferences
copied['preferences']['theme'] = 'dark'
print(f"Original: {original}") # Original is unexpectedly modified
print(f"Copy: {copied}")
The copy() method only duplicates the top level. The nested preferences dictionary is a shared reference, so changing the theme in the copy also alters the original. The corrected implementation below shows how to prevent this.
import copy
original = {'user': 'John', 'preferences': {'theme': 'light', 'font_size': 12}}
copied = copy.deepcopy(original)
# Modify nested preferences
copied['preferences']['theme'] = 'dark'
print(f"Original: {original}") # Original remains unchanged
print(f"Copy: {copied}")
The corrected code uses copy.deepcopy() to create a fully independent duplicate of the original dictionary. This function recursively copies all nested objects, so modifying the preferences in the copied dictionary doesn't affect the original.
- This ensures the two dictionaries are completely separate, preventing unintended changes.
- Always use
deepcopy()when your dictionary contains other mutable objects, like lists or other dictionaries, to avoid this common pitfall.
Troubleshooting KeyError when accessing copied dictionaries
A KeyError is a common runtime error that pops up when you try to access a dictionary key that doesn't exist. This can easily happen with copied dictionaries, especially if you only copied a subset of the original's keys.
The following code demonstrates how attempting to access a key that wasn't part of the copy operation will crash your program.
source = {'name': 'John', 'age': 30, 'city': 'New York'}
# Copy only specific keys
partial = {k: source[k] for k in ['name', 'age']}
# Trying to access a key that wasn't copied
location = partial['city'] # Will raise KeyError
print(f"Name: {partial['name']}, Location: {location}")
The partial dictionary is created with only the name and age keys. Attempting to access partial['city'] fails because that key was never copied from the source dictionary, causing a KeyError. The corrected implementation below demonstrates a safer way to handle this.
source = {'name': 'John', 'age': 30, 'city': 'New York'}
# Copy only specific keys
partial = {k: source[k] for k in ['name', 'age']}
# Safely access keys with get() method
location = partial.get('city', 'Unknown')
print(f"Name: {partial['name']}, Location: {location}")
The corrected code avoids a KeyError by using the get() method. Instead of crashing when a key is missing, partial.get('city', 'Unknown') safely returns a default value—in this case, 'Unknown'. This makes your code more resilient.
- Use this technique whenever you're not certain a key will exist. It's especially useful when working with data from external sources or after filtering a dictionary, preventing unexpected crashes.
Real-world applications
Beyond avoiding bugs, these copying methods are fundamental for building practical features like configurable user profiles and dynamic application settings.
Creating user profiles with default settings
The copy() method is ideal for creating new user profiles from a template, as it lets you establish a baseline of default settings and then customize each user's configuration independently.
default_settings = {'theme': 'light', 'notifications': True, 'language': 'English'}
user1_settings = default_settings.copy()
user1_settings['theme'] = 'dark'
user2_settings = default_settings.copy()
user2_settings['language'] = 'Spanish'
print(f"User 1: {user1_settings}")
print(f"User 2: {user2_settings}")
This example starts with a default_settings template. Using default_settings.copy() creates new, independent dictionaries for each user. This ensures that customizations for one user don't accidentally affect another.
- Modifying the
themeforuser1_settingsleaves the original template anduser2_settingsuntouched. - Likewise, changing the
languageforuser2_settingsonly applies to that user.
This approach is a clean way to manage unique configurations derived from a shared starting point.
Managing environment configurations with deepcopy()
The copy.deepcopy() function is essential for managing different application environments because it creates fully independent configurations, ensuring changes to nested settings in one environment don't affect another.
import copy
# Base configuration
config = {'debug': False, 'database': {'host': 'localhost'}}
# Create environment-specific configurations
dev_config = copy.deepcopy(config)
dev_config['debug'] = True
prod_config = copy.deepcopy(config)
prod_config['database']['host'] = 'db.example.com'
print(f"Dev config: {dev_config}")
print(f"Prod config: {prod_config}")
This example shows how to safely create distinct configurations from a single template. The copy.deepcopy() function is used to build dev_config and prod_config, ensuring each is a standalone object.
- Notice that modifying the nested
databasedictionary insideprod_confighas no impact on the originalconfigor thedev_config. - This happens because
deepcopy()recursively duplicates every item, including the innerdatabasedictionary. Each configuration gets its own version, preventing changes in one from leaking into another.
Get started with Replit
Now, turn these concepts into a real tool. Tell Replit Agent to “build a config manager for dev and prod environments” or “create a user profile system with customizable templates.”
It will write the code, test for errors, and deploy your application for you. Start building with Replit and bring your idea to life.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.


.png)
.png)