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.

How to copy a dictionary in Python
Published on: 
Tue
Mar 10, 2026
Updated on: 
Fri
Mar 13, 2026
The Replit Team

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 to original doesn't change the copied dictionary.
  • 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 the copy() method.
  • As the example shows, when you call original.clear(), the copied dictionary 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 to False, 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 age in the nested dictionary also changes it in the shallow copy. This happens because both dictionaries point to the very same info dictionary.
  • The deep copy, however, remains unchanged. It has its own separate info dictionary, 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 city in 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 to None.
  • Inside the function, check if the argument is None and, 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, or None (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 in keyword, like if '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 None and 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 theme for user1_settings leaves the original template and user2_settings untouched.
  • Likewise, changing the language for user2_settings only 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 database dictionary inside prod_config has no impact on the original config or the dev_config.
  • This happens because deepcopy() recursively duplicates every item, including the inner database dictionary. 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.

Get started free

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.

Get started for free

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.