Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallFor an ordinary shallow merge, use first | second on Python 3.9 and later. It returns a new dictionary, and when a key appears in both operands, the value from second wins. To mutate an existing dictionary, use first |= second or first.update(second). Projects supporting Python 3.5–3.8 can use {**first, **second}.
Those forms solve different problems. A merge may mean making a copy, updating in place, layering live mappings, recursively combining nested dictionaries, or applying a custom collision rule. Choose the operation that matches the required behavior.
Quick reference
| Need | Recommended form | Version | Mutates an input? | Collision rule |
|---|---|---|---|---|
| New dictionary from two dictionaries | d1 | d2 |
3.9+ | No (top level) | Right-hand value wins |
| Update the left dictionary | d1 |= d2 |
3.9+ | Yes | Right-hand value wins |
| In-place update with broad compatibility | d1.update(d2) |
All commonly supported versions | Yes | Right-hand value wins |
| New dictionary on Python 3.5–3.8 | {**d1, **d2} |
3.5+ | No (top level) | Later entry wins |
| Layered, live lookup | ChainMap(overrides, defaults) |
3.3+ | No flattening | First mapping containing the key wins |
| Recursive nested merge | Application-specific function | Any | Depends on implementation | Must be defined by you |
The dictionary operators are documented in the Python dictionary documentation. The | and |= operators were specified by PEP 584; unpacking comes from PEP 448.
Merge dictionaries with |
defaults = {"theme": "light", "retries": 2}
overrides = {"theme": "dark", "debug": True}
settings = defaults | overrides
print(settings)
# {'theme': 'dark', 'retries': 2, 'debug': True}
| creates a new ordinary dictionary. Neither operand is changed, and the right-hand value replaces the left-hand value for duplicate keys. Therefore, operand order is a policy decision: defaults | user_settings lets users override defaults, while reversing the operands lets defaults override users.
#1 Best Overall
Dictionary insertion order is guaranteed from Python 3.7 onward. Existing keys retain their position when their value is replaced; new keys from the right-hand operand appear in that operand’s iteration order. The operation is not commutative when keys overlap, so a | b can differ from b | a.
Binary | is deliberately narrow: both operands must be dictionaries or dictionary subclasses. It is not a general “any mapping” operator. For a custom mapping or an iterable of pairs, use update() or augmented assignment instead.
Update in place with |= or update()
settings = {"theme": "light", "retries": 2}
settings |= {"theme": "dark", "debug": True}
print(settings)
# {'theme': 'dark', 'retries': 2, 'debug': True}
|= changes the left-hand dictionary. It accepts a mapping or an iterable of two-item key-value pairs:
settings |= {"debug": True}
settings |= [("timeout", 30)]
Augmented assignment is a statement, not an expression. This is invalid:
Recommended Free Tools
result = settings |= overrides # SyntaxError
The method equivalent is:
settings.update(overrides)
dict.update() accepts a mapping, an object with a keys() method, an iterable of pairs, or keyword arguments, and it returns None. Consequently, do not write result = data.update(other) expecting result to be a dictionary.
Rank #2
data = {"a": 1}
data.update({"b": 2})
data.update([("c", 3), ("d", 4)])
data.update(user_name="Ada")
data.update({42: "answer"}) # non-string key through a mapping
Keyword arguments must have string keys. A mapping or pair iterable is required for keys such as integers. An iterator supplied to update() is consumed, so reusing the same exhausted iterator will not add more items.
Use dictionary unpacking for older Python versions
merged = {**first, **second}
Dictionary unpacking works from Python 3.5 and is the usual expression-style choice for Python 3.5–3.8. Later entries override earlier entries, and the result is an ordinary dict:
merged = {
**defaults,
**environment_settings,
"debug": True,
}
It is shallow and requires mapping-compatible objects for **. It should not be confused with function-call unpacking. Duplicate keys in a dictionary display are resolved by the later value, whereas duplicate keyword arguments in a call raise TypeError:
data = {**{"x": 1}, **{"x": 2}} # {'x': 2}
func(**{"x": 1}, **{"x": 2}) # TypeError
Copy, then update
merged = first.copy()
merged.update(second)
This explicit form is useful when readers need to see the mutation boundary, when supporting older Python versions, or when validation and other processing must occur between copying and updating. It also avoids presenting | to codebases that have not adopted Python 3.9 syntax.
The copy is shallow, not deep. The outer dictionary is new, but nested mutable values remain shared references. Python’s distinction between shallow and deep copying is described in the copy module documentation:
first = {"options": {"timeout": 10}}
merged = first | {"debug": True}
merged["options"]["timeout"] = 30
print(first["options"]["timeout"])
# 30
Use explicit recursive copying or copy.deepcopy() only when independent nested objects are actually required; deep copying can copy more data than intended.
Merge more than two dictionaries
For a small, fixed number of dictionaries, chaining is clear:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsmerged = first | second | third
For a collection of dictionaries, an explicit loop avoids repeatedly constructing intermediate results and gives you a place to validate keys:
merged = {}
for current in dictionaries:
merged |= current # Python 3.9+
On older versions, replace the body with merged.update(current). This is an allocation and clarity trade-off, not a universal speed ranking; actual performance depends on dictionary sizes, overlap, Python version, and workload.
reduce() is another valid expression:
from functools import reduce
from operator import or_
merged = reduce(or_, dictionaries, {})
The explicit loop is generally easier to inspect, debug, and extend. reduce() applies a two-argument function cumulatively from left to right, as documented for functools.reduce.
Choose a collision policy deliberately
Rightmost-wins behavior is the default for |, |=, update(), and unpacking. If that is not the application’s rule, implement the rule explicitly.
First value wins
def merge_first_wins(*dicts):
result = {}
for current in dicts:
for key, value in current.items():
result.setdefault(key, value)
return result
Alternatively, reverse the inputs and use ordinary right-wins merging:
result = {}
for current in reversed(dicts):
result |= current
Reject duplicate keys
def merge_without_conflicts(*dicts):
result = {}
for current in dicts:
overlap = result.keys() & current.keys()
if overlap:
raise KeyError(f"Duplicate keys: {sorted(overlap, key=repr)}")
result.update(current)
return result
Collect every value
from collections import defaultdict
def merge_collect(*dicts):
result = defaultdict(list)
for current in dicts:
for key, value in current.items():
result[key].append(value)
return dict(result)
Add numeric counts
from collections import Counter
totals = Counter({"apples": 3}) + Counter({"apples": 2, "oranges": 4})
# Counter({'apples': 5, 'oranges': 4})
Counter is designed for counts and has specialized arithmetic semantics; it is not a drop-in replacement for general dictionary merging. See the Counter documentation.
Understand shallow versus deep merging
Built-in dictionary operations replace the complete value at a key. They do not recursively merge nested dictionaries:
left = {
"database": {"host": "localhost", "port": 5432}
}
right = {
"database": {"port": 5433}
}
print(left | right)
# {'database': {'port': 5433}}
If nested mappings should be combined, define that policy yourself. This example recurses only when both values are mappings:
Free tools Windows power users keep installed
One-click scans. No signup required.
Best Value
from collections.abc import Mapping
def deep_merge(left, right):
result = left.copy()
for key, right_value in right.items():
left_value = result.get(key)
if isinstance(left_value, Mapping) and isinstance(right_value, Mapping):
result[key] = deep_merge(left_value, right_value)
else:
result[key] = right_value
return result
print(deep_merge(left, right))
# {'database': {'host': 'localhost', 'port': 5433}}
This function’s policy is intentionally narrow: nested mappings recurse; a mapping-versus-scalar conflict uses the right-hand value; lists and sets are replaced; and other type conflicts are not errors. Configuration formats may instead concatenate lists, union sets, reject type changes, or detect cycles. There is no universal built-in deep-merge rule because those meanings are application-specific, a point discussed in PEP 584.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Use ChainMap when you need a live layered view
from collections import ChainMap
defaults = {"theme": "light", "retries": 2}
overrides = {"theme": "dark"}
settings = ChainMap(overrides, defaults)
print(settings["theme"])
# dark
ChainMap searches mappings from first to last and returns the first matching key. It does not flatten them into one dictionary. Changes to the underlying mappings are visible through the view; writes, updates, and deletions affect only the first mapping. This makes it useful for defaults plus environment or command-line overrides, nested scopes, and temporary overlays. Its behavior is documented at collections.ChainMap.
When an independent flattened dictionary is required, materialize it explicitly:
flattened = dict(settings)
Important edge cases
Mutation and precedence
base.update(overrides) changes base; base | overrides does not change either top-level input. In both cases, nested objects may still be shared. Always put the higher-precedence source on the right for right-wins operations.
General mappings and dictionary subclasses
{**d1, **d2} and dictionary union produce a regular dictionary rather than promising to preserve a subclass such as defaultdict or a custom dictionary type. Binary | may reject a general mapping even when update() would accept it. Use the operation whose operand contract matches your input.
Invalid keys and changing during iteration
Merge operations still require hashable dictionary keys; they do not make lists or other unhashable objects valid keys. Avoid modifying a dictionary while iterating over its dynamic views to build another merge source: Python can raise RuntimeError or produce incomplete iteration. Dictionary view behavior is described in the dictionary-view documentation.
Quick Recap
Python-version compatibility
| Python release | Preferred syntax |
|---|---|
| 3.9 and later | d1 | d2 for a new dictionary |
| 3.9 and later, in place | d1 |= d2 |
| 3.5–3.8 | {**d1, **d2} |
| Any supported version where explicit mutation is clearest | d1.copy(); result.update(d2) |
Selection checklist
- Need a new flattened dictionary on Python 3.9+? Use
d1 | d2. - Is changing the left dictionary intentional? Use
|=orupdate(). - Must the project run on Python 3.8 or earlier? Use unpacking or copy plus
update(). - Which source should win on duplicate keys? Put that source last, or write a different collision policy.
- Are the operands general mappings or pair iterables? Prefer
update()or|=. - Do nested mappings need to survive and combine? Write a recursive function with explicit rules.
- Do you need live precedence without copying? Use
ChainMap. - Do you need independent nested objects? Copy nested values deliberately; a normal merge is shallow.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

