How to merge two dictionaries in Python

Learn how to merge two dictionaries in Python. Explore various methods, tips, real-world applications, and common error debugging.

How to merge two dictionaries in Python
Published on: 
Thu
Feb 12, 2026
Updated on: 
Mon
Apr 13, 2026
The Replit Team

You often need to merge dictionaries in Python, a key task when you combine data from different sources. Python provides several efficient methods, from the update() method to the modern union operator (|).

In this article, you'll explore several techniques, complete with performance tips and real-world applications. You'll also find debugging advice to help you select the right approach and avoid common pitfalls.

Using the update() method

dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
dict1.update(dict2)
print(dict1)--OUTPUT--{'a': 1, 'b': 2, 'c': 3, 'd': 4}

The update() method is a classic approach that modifies the dictionary it's called on. This is an in-place operation, meaning dict1 is changed directly without creating a new dictionary object. This is particularly useful when:

  • You want to conserve memory by avoiding new object creation.
  • You're updating a data structure that's referenced elsewhere in your code.

If there are overlapping keys, the values from the dictionary passed to update()—in this case, dict2—will overwrite the existing ones in dict1. This behavior is important to understand when accessing dictionary elements in Python.

Common dictionary merging techniques

If you need to create a new dictionary instead of modifying one, you can use the ** operator, the dict() constructor, or a comprehension.

Using the ** unpacking operator

dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
merged_dict = {**dict1, **dict2}
print(merged_dict)--OUTPUT--{'a': 1, 'b': 2, 'c': 3, 'd': 4}

The double-asterisk (**) operator unpacks the key-value pairs from dict1 and dict2 into a new dictionary literal. This method is clean, readable, and creates a new dictionary, leaving the original ones unchanged. It's a popular choice for its conciseness.

  • If keys overlap, the value from the rightmost dictionary—in this case, dict2—takes precedence.
  • This approach is generally preferred for its clarity when you don't need to modify a dictionary in-place.

Using the dict() constructor

dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
merged_dict = dict(dict1)
merged_dict.update(dict2)
print(merged_dict)--OUTPUT--{'a': 1, 'b': 2, 'c': 3, 'd': 4}

This technique is a two-step process. First, you create a shallow copy of dict1 using the dict() constructor. Then, you call update() on this new copy to add the key-value pairs from dict2. The result is a new dictionary, so your original dictionaries remain untouched.

  • This method is more explicit than using the ** operator.
  • It's particularly handy when you need to build up a dictionary in several steps from multiple sources.

Using dictionary comprehension

dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
merged_dict = {k: v for d in [dict1, dict2] for k, v in d.items()}
print(merged_dict)--OUTPUT--{'a': 1, 'b': 2, 'c': 3, 'd': 4}

Dictionary comprehensions offer a powerful and flexible way to merge dictionaries. This technique iterates through a list of your dictionaries—in this case, [dict1, dict2]—and then unpacks the key-value pairs from each one to build a new dictionary. It’s a compact and very “Pythonic” approach.

  • This method is highly customizable. You can add conditional logic within the comprehension to control which key-value pairs are included.
  • Like other non-in-place methods, it creates a new dictionary. The value from the last dictionary in the sequence will overwrite any previous ones for the same key.

Advanced dictionary merging techniques

Beyond the common methods, Python provides advanced tools for more nuanced tasks, including collections.ChainMap, the | operator, and strategies for deep merging.

Using collections.ChainMap

from collections import ChainMap
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'b': 4}
chain_map = ChainMap(dict2, dict1)
print(dict(chain_map))--OUTPUT--{'a': 1, 'b': 4, 'c': 3}

The collections.ChainMap class offers a different take on merging. Instead of creating a new dictionary, it groups multiple dictionaries into a single, updatable view. This approach is memory-efficient because it doesn't copy the underlying data; it just links the original dictionaries.

  • When you access a key, ChainMap searches each dictionary in the order you provided them until it finds a match.
  • In the example, dict2 is checked first, so the value for the key 'b' comes from dict2.
  • This is great for managing layered configurations, like default settings that can be overridden by user-specific ones.

Using the | operator (Python 3.9+)

dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'b': 4}
merged_dict = dict1 | dict2
print(merged_dict)--OUTPUT--{'a': 1, 'b': 4, 'c': 3}

Introduced in Python 3.9, the union operator (|) offers a clean and modern syntax for merging dictionaries. It functions much like the ** unpacking operator, creating a new dictionary without altering the original ones. This approach makes your code more readable and concise.

  • If keys overlap, the value from the dictionary on the right side of the operator takes precedence.
  • For in-place merging, you can use the augmented assignment operator, |=, which modifies the dictionary directly.

Deep merging nested dictionaries

def deep_merge(d1, d2):
result = d1.copy()
for k, v in d2.items():
if k in result and isinstance(result[k], dict) and isinstance(v, dict):
result[k] = deep_merge(result[k], v)
else:
result[k] = v
return result

dict1 = {'a': 1, 'b': {'x': 10, 'y': 20}}
dict2 = {'c': 3, 'b': {'z': 30, 'y': 50}}
print(deep_merge(dict1, dict2))--OUTPUT--{'a': 1, 'b': {'x': 10, 'y': 50, 'z': 30}, 'c': 3}

When your dictionaries contain other dictionaries, standard merging methods fall short. They perform a shallow merge, replacing an entire nested dictionary instead of combining its contents. Deep merging is the solution for recursively combining these nested structures, preserving data from both.

This custom deep_merge function works by:

  • Checking if a key exists in both dictionaries and if both values are also dictionaries.
  • If so, it calls itself to merge the inner dictionaries.
  • Otherwise, it simply overwrites the value, with the second dictionary's value taking precedence.

Move faster with Replit

Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. You can move from learning individual techniques, like using update() or ChainMap, to building complete applications.

Instead of piecing those techniques together yourself, you can describe the app you want to build and Agent 4 will take it from idea to working product. For example, you could create:

  • A configuration utility that merges default settings with user-specific overrides from separate dictionaries.
  • A data processor that combines multiple JSON files into a single, unified dictionary for an application.
  • A dashboard backend that aggregates user profile data and preferences into a single view.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Merging dictionaries is usually straightforward, but a few common challenges involving key collisions, accidental modifications, and nested data can trip you up.

Handling key collisions when merging dictionaries

A primary challenge is handling key collisions, which happen when dictionaries share keys. The default behavior in most merging methods is that the last value wins, which can cause you to lose data without warning.

  • With the ** operator, | operator, or update() method, the value from the rightmost or second dictionary always takes precedence.
  • To avoid accidental data loss, you must be conscious of the merge order and which dictionary's values will persist in the final result.

Avoiding unintended dictionary modifications with update()

The update() method's efficiency comes from modifying a dictionary in-place, but this can introduce sneaky bugs. Any changes you make to a dictionary will affect every part of your code that references it.

  • For instance, updating a global configuration dictionary directly could have unintended consequences across your entire application.
  • If you don't want to alter the original, it's safer to create a copy first or use a method that returns a new dictionary, such as {**dict1, **dict2}.

Merging nested dictionaries correctly

When you're working with nested dictionaries, standard merging techniques perform a shallow merge. This means an entire inner dictionary gets replaced instead of having its contents combined, which is a common way to lose data.

  • This is a frequent issue when processing complex JSON objects or layered configuration settings.
  • The solution is to use a deep merge, which recursively travels through the nested structure to combine data at every level, ensuring no information is lost.

Handling key collisions when merging dictionaries

Key collisions are a common source of bugs when merging dictionaries. Because the value from the last dictionary in a merge operation takes precedence, you can accidentally overwrite important data. This is especially tricky when combining user settings with default configurations.

The following code demonstrates how easily this can happen, where user preferences are unintentionally overridden by the default settings.

# Trying to add default settings while preserving user preferences
user_prefs = {'theme': 'dark', 'font_size': 12}
default_prefs = {'theme': 'light', 'font_size': 10, 'notifications': True}
merged_prefs = {**user_prefs, **default_prefs}
print(merged_prefs)

Because default_prefs is unpacked second, its values for theme and font_size overwrite the user's settings, causing data loss. The following code snippet demonstrates the correct way to merge these dictionaries.

# Correct order: defaults first, then user preferences to override
user_prefs = {'theme': 'dark', 'font_size': 12}
default_prefs = {'theme': 'light', 'font_size': 10, 'notifications': True}
merged_prefs = {**default_prefs, **user_prefs}
print(merged_prefs)

The solution is to reverse the merge order. By unpacking default_prefs first and user_prefs second, you ensure the user's choices for theme and font_size overwrite the defaults. This pattern is crucial when you're layering configurations, such as applying user settings over a system's default values. The dictionary on the right always wins in a key collision, so its values are the ones that persist in the final merged dictionary.

Avoiding unintended dictionary modifications with update()

The update() method modifies a dictionary directly, which is efficient but can lead to unintended changes. If another part of your code relies on the original dictionary, it will be unexpectedly altered. The following code demonstrates this exact problem in action.

def add_settings(user_config, extra_settings):
user_config.update(extra_settings)
return user_config

original_config = {'debug': False, 'timeout': 30}
new_config = add_settings(original_config, {'verbose': True})
print("New config:", new_config)
print("Original config:", original_config) # Original is modified too!

The add_settings function directly alters the original_config dictionary passed into it. This side effect occurs because update() works in-place, a behavior that can introduce subtle bugs. The following code demonstrates a safer approach to avoid this.

def add_settings(user_config, extra_settings):
return {**user_config, **extra_settings}

original_config = {'debug': False, 'timeout': 30}
new_config = add_settings(original_config, {'verbose': True})
print("New config:", new_config)
print("Original config:", original_config) # Original remains unchanged

The corrected add_settings function now uses the ** unpacking operator to create and return a new dictionary. This leaves the original_config untouched, preventing the side effect of unintended modification. This approach is much safer, especially in functions that might be reused, as it preserves the original data. It’s a good practice to avoid modifying inputs unless the function is explicitly designed to do so.

Merging nested dictionaries correctly

When you merge dictionaries that contain other dictionaries, common methods like the ** operator only perform a shallow merge. This replaces the entire inner dictionary instead of combining its contents, which often leads to unintended data loss. The following code demonstrates this problem.

settings = {'display': {'theme': 'dark'}, 'audio': {'volume': 80}}
updates = {'display': {'font': 'Arial'}, 'audio': {'mute': False}}
merged = {**settings, **updates}
print(merged) # Nested dictionaries are replaced, not merged

The updates dictionary's nested values completely replace those in settings. As a result, the 'theme' key from the original display dictionary is lost. The following code demonstrates the correct way to combine these nested structures.

def deep_merge(dict1, dict2):
result = dict1.copy()
for key, value in dict2.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = value
return result

settings = {'display': {'theme': 'dark'}, 'audio': {'volume': 80}}
updates = {'display': {'font': 'Arial'}, 'audio': {'mute': False}}
merged = deep_merge(settings, updates)
print(merged)

The deep_merge function recursively combines nested data, ensuring nothing is lost. It iterates through the second dictionary, and if a key exists in both and the values are also dictionaries, it calls itself to merge them. Otherwise, it just updates the value.

This approach is essential when you're working with complex configurations or JSON files where you need to combine nested information instead of replacing it.

Real-world applications

Understanding these challenges prepares you to apply dictionary merging techniques to common, real-world programming tasks.

Merging default and user configurations with ** operator

The ** operator is particularly useful for managing application settings, where you can cleanly overlay user preferences on top of a default configuration.

default_config = {'debug': False, 'log_level': 'INFO', 'max_retries': 3}
user_config = {'log_level': 'DEBUG', 'timeout': 30}
final_config = {**default_config, **user_config}
print(final_config)

In this common pattern, the ** operator merges the two dictionaries. Since user_config comes second, its settings take priority. Notice how the log_level from user_config ('DEBUG') overwrites the one from default_config ('INFO') in the final output.

At the same time, keys that are unique to each dictionary, like max_retries and timeout, are both included. This creates a complete configuration that applies user preferences over a set of defaults without altering the original default_config or user_config dictionaries.

Combining data from multiple API responses

Merging dictionaries is also essential when you're combining data from different API responses to create a unified view, such as a complete user profile.

# Simulate responses from different API endpoints
user_data = {'id': 123, 'name': 'John Doe', 'email': 'john@example.com'}
order_data = {'order_id': 456, 'user_id': 123, 'items': 5, 'total': 99.95}

# Merge the data to create a complete user profile
user_profile = {**user_data}
if user_data['id'] == order_data['user_id']:
user_profile['orders'] = {'count': order_data['items'], 'value': order_data['total']}

print(user_profile)

This code demonstrates a practical way to combine data from different sources, like user_data and order_data. It’s more than a simple merge—it’s a selective combination that ensures data integrity.

  • First, a new user_profile is created as a copy of user_data using the ** operator.
  • It then validates that both dictionaries refer to the same user by comparing the id and user_id fields.
  • Finally, it adds relevant order information as a new nested dictionary, creating a unified profile without altering the original data sources.

Get started with Replit

Now, put your knowledge into practice. Tell Replit Agent to "build a tool that merges a default config file with a user's settings" or "create a script that combines API data into a single user profile."

Replit Agent will write the code, test for errors, and deploy your application. Start building with Replit.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.