Knowing how to python merge dictionaries is one of the most practical skills you'll use as a Python developer. Whether you're combining configuration objects, aggregating API responses, or building data pipelines, the need to python combine dicts comes up constantly. Python offers several ways to merge dictionaries — from classic loops to the sleek python 3.9 dict merge operator — and each approach has its own strengths. In this guide, you'll learn every major method to python merge dictionaries, when to use each one, and exactly how they behave with real examples.

A Python dictionary is a mutable, unordered collection of key-value pairs. When you python merge dictionaries, you're combining two or more of these collections into one. Let's dive into every way Python lets you do it.


Using the update() Method to Python Update Dictionary

The update() method is the classic, battle-tested way to python update dictionary values by merging another dict into it. It modifies the dictionary in place, meaning it changes the original dict rather than creating a new one.

user = {"name": "Alice", "age": 30}
extra = {"city": "Berlin", "age": 31}

user.update(extra)
print(user)
# {'name': 'Alice', 'age': 31, 'city': 'Berlin'}

Notice that age was overwritten by the value from extra. When you python update dictionary with overlapping keys, the right-hand dictionary always wins. This is a key behavior to remember across all methods for merging dicts in Python.

The update() method also accepts keyword arguments:

settings = {"theme": "dark", "lang": "en"}
settings.update(font_size=14, lang="fr")
print(settings)
# {'theme': 'dark', 'lang': 'fr', 'font_size': 14}

This makes update() flexible when you want to inject a few extra keys without creating a temporary dictionary. It's the most widely supported way to python update dictionary and works in Python 2, 3, and every version in between.


Using the ** Unpacking Operator to Python Merge Dictionaries

The python ** unpacking dict syntax lets you merge dictionaries inside a new {} literal. This approach creates a brand new dictionary and leaves the originals untouched — making it great when you want immutability.

base_config = {"host": "localhost", "port": 5432}
override = {"port": 9999, "ssl": True}

merged = {**base_config, **override}
print(merged)
# {'host': 'localhost', 'port': 9999, 'ssl': True}

The python ** unpacking dict technique works by spreading key-value pairs into the new dictionary from left to right. If the same key appears in both dicts, the rightmost value wins — here port becomes 9999.

You can unpack more than two dictionaries at once:

a = {"x": 1}
b = {"y": 2}
c = {"z": 3, "x": 99}

result = {**a, **b, **c}
print(result)
# {'x': 99, 'y': 2, 'z': 3}

This is one of the most popular ways to python combine dicts because it's readable, concise, and produces a fresh dictionary. It's been available since Python 3.5.


Python 3.9 Dict Merge with the | Union Operator

Python 3.9 introduced a dedicated python dict union operator — the | symbol — specifically for merging dictionaries. It's the cleanest, most expressive syntax available for the job.

inventory_a = {"apples": 10, "bananas": 5}
inventory_b = {"bananas": 8, "oranges": 12}

combined = inventory_a | inventory_b
print(combined)
# {'apples': 10, 'bananas': 8, 'oranges': 12}

The python 3.9 dict merge operator creates a new dictionary. bananas gets the value from the right-hand dict (8), following the same "rightmost wins" rule.

Python 3.9 also introduced |= for in-place merging — the python update dictionary equivalent of the union operator:

defaults = {"timeout": 30, "retries": 3}
overrides = {"retries": 5, "verbose": True}

defaults |= overrides
print(defaults)
# {'timeout': 30, 'retries': 5, 'verbose': True}

The |= operator modifies defaults in place, just like update(), but with cleaner syntax. If you're running Python 3.9 or later, this is the recommended way to python merge dictionaries — it clearly expresses intent and reads like natural language.

Check PEP 584 for the full specification of the python dict union operator.


Using a for Loop to Python Combine Dicts

Sometimes you need full control over how merging works — for example, summing values instead of overwriting them. A for loop gives you that control when you need to python combine dicts with custom logic.

scores_week1 = {"Alice": 45, "Bob": 38, "Carol": 52}
scores_week2 = {"Alice": 50, "Bob": 41, "David": 60}

combined_scores = dict(scores_week1)

for player, score in scores_week2.items():
    if player in combined_scores:
        combined_scores[player] += score  # Sum instead of overwrite
    else:
        combined_scores[player] = score

print(combined_scores)
# {'Alice': 95, 'Bob': 79, 'Carol': 52, 'David': 60}

This pattern is essential when the default "overwrite on conflict" behavior isn't what you want. No built-in merge operator can sum, average, or apply custom logic on duplicate keys — that's the loop's domain.


Using dict() Constructor with ** Unpacking

Another readable way to python combine dicts is passing ** unpacking directly into the dict() constructor:

profile = {"username": "jdoe", "email": "[email protected]"}
prefs = {"newsletter": True, "email": "[email protected]"}

full_profile = dict(**profile, **prefs)
print(full_profile)
# {'username': 'jdoe', 'email': '[email protected]', 'newsletter': True}

This behaves identically to {**profile, **prefs} but can feel more explicit in some codebases. Note that when you python combine dicts this way, all keys must be valid Python identifiers — a minor constraint that rarely matters in practice.


How to Python Merge Nested Dicts

The methods above perform shallow merges — they only merge top-level keys. When dictionaries have nested dictionaries as values, a shallow merge will overwrite the nested dict entirely rather than merging it recursively.

config_a = {"db": {"host": "localhost", "port": 5432}, "debug": False}
config_b = {"db": {"port": 9999, "name": "mydb"}, "debug": True}

# Shallow merge — LOSES config_a's db host!
shallow = config_a | config_b
print(shallow)
# {'db': {'port': 9999, 'name': 'mydb'}, 'debug': True}

To python merge nested dicts properly, you need a recursive function:

def deep_merge(base, override):
    result = dict(base)
    for key, value in override.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

config_a = {"db": {"host": "localhost", "port": 5432}, "debug": False}
config_b = {"db": {"port": 9999, "name": "mydb"}, "debug": True}

merged = deep_merge(config_a, config_b)
print(merged)
# {'db': {'host': 'localhost', 'port': 9999, 'name': 'mydb'}, 'debug': True}

Now host from config_a is preserved while port and name are merged in from config_b. This is the correct approach when you python merge nested dicts in real-world applications like configuration management systems.


Using collections.ChainMap to Python Combine Dicts

collections.ChainMap is a unique approach to combining dicts. Instead of creating a new merged dictionary, it creates a view over multiple dicts, making lookups search through them in order.

from collections import ChainMap

defaults = {"theme": "light", "lang": "en", "notifications": True}
user_prefs = {"theme": "dark", "lang": "fr"}

view = ChainMap(user_prefs, defaults)
print(view["theme"])        # 'dark'  — from user_prefs
print(view["notifications"]) # True  — falls back to defaults

ChainMap doesn't copy any data — it just links the dicts together. This makes it extremely memory-efficient when you python combine dicts for read-heavy scenarios like layered configuration (user settings → app defaults → system defaults).

Updates via ChainMap only affect the first (leftmost) mapping:

view["lang"] = "de"
print(user_prefs)  # {'theme': 'dark', 'lang': 'de'}
print(defaults)    # {'theme': 'light', 'lang': 'en', 'notifications': True}

Quick Comparison: All Methods to Python Merge Dictionaries

MethodCreates New DictPython VersionIn-PlaceCustom Conflict Logic
update()No2+YesNo
{\*\*a, \*\*b}Yes3.5+NoNo
a | bYes3.9+NoNo
a |= bNo3.9+YesNo
for loopOptionalAnyOptionalYes
ChainMapNo (view)3.3+NoNo

Full Working Example: Python Merge Dictionaries

This example brings together multiple merge techniques in a realistic scenario — merging layered app configurations with nested dict support.

from collections import ChainMap


# --- Utility: Deep merge for nested dicts ---
def deep_merge(base, override):
    result = dict(base)
    for key, value in override.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


# --- System defaults ---
system_defaults = {
    "app": {"name": "MyApp", "version": "1.0", "debug": False},
    "db": {"host": "localhost", "port": 5432, "timeout": 30},
    "cache": {"enabled": True, "ttl": 300},
}

# --- Environment overrides (e.g., staging) ---
env_overrides = {
    "app": {"debug": True, "version": "1.1-beta"},
    "db": {"host": "staging-db.internal", "timeout": 10},
}

# --- User-supplied overrides at runtime ---
runtime_config = {
    "db": {"port": 5433},
    "cache": {"ttl": 60},
    "feature_flags": {"new_ui": True},
}

# Step 1: Deep merge system defaults with environment overrides
stage_config = deep_merge(system_defaults, env_overrides)

# Step 2: Apply runtime config on top using Python 3.9 | operator (shallow)
#         then deep merge to handle nested keys properly
final_config = deep_merge(stage_config, runtime_config)

# Step 3: Use ChainMap for a fallback read-only view (no data copy)
fallback_view = ChainMap(final_config, system_defaults)

print("=== Final Merged Configuration ===")
for section, value in final_config.items():
    print(f"{section}: {value}")

print("\n=== ChainMap fallback lookup ===")
print("cache.ttl (final):", final_config["cache"]["ttl"])
print("app.name (falls back to system default):", fallback_view["app"]["name"])

# Step 4: In-place update using |= (Python 3.9+)
analytics = {"analytics": {"enabled": False}}
final_config |= analytics
print("\n=== After |= update ===")
print("analytics:", final_config["analytics"])

Output:

=== Final Merged Configuration ===
app: {'name': 'MyApp', 'version': '1.1-beta', 'debug': True}
db: {'host': 'staging-db.internal', 'port': 5433, 'timeout': 10}
cache: {'enabled': True, 'ttl': 60}
feature_flags: {'new_ui': True}

=== ChainMap fallback lookup ===
cache.ttl (final): 60
app.name (falls back to system default): MyApp

=== After |= update ===
analytics: {'enabled': False}

This example demonstrates every core concept: deep_merge for python merge nested dicts, the | and |= python dict union operator from Python 3.9, and ChainMap for a memory-efficient layered view — all working together to python merge dictionaries in a production-style configuration system.


Ready to go deeper? This guide is part of a complete Python tutorial series covering everything from basics to advanced topics. Whether you're just starting out or looking to sharpen your skills, the full series has you covered. Learn the full Python tutorial here.