How to Diff Two Dicts in Python for Config Drift
Recursively compare two dictionaries and report added, removed, and changed keys with their old and new values for debugging configuration drift.
Python code
32 linesdef diff_dicts(a, b, path=""):
differences = []
for key in a.keys() | b.keys():
new_path = f"{path}.{key}" if path else key
if key not in a:
differences.append((new_path, "<missing>", b[key], "added"))
elif key not in b:
differences.append((new_path, a[key], "<missing>", "removed"))
elif isinstance(a[key], dict) and isinstance(b[key], dict):
differences.extend(diff_dicts(a[key], b[key], new_path))
elif a[key] != b[key]:
differences.append((new_path, a[key], b[key], "changed"))
return differences
if __name__ == "__main__":
config1 = {
"server": {"host": "localhost", "port": 8080, "timeout": 30},
"debug": True,
"name": "app",
}
config2 = {
"server": {"host": "localhost", "port": 9090, "max_conns": 100},
"debug": False,
"version": "2.0",
}
for path, old, new, change_type in diff_dicts(config1, config2):
print(f"{change_type.upper()}: {path} = {old!r} -> {new!r}")
Output
CHANGED: server.port = 8080 -> 9090
REMOVED: server.timeout = 30 -> <missing>
ADDED: server.max_conns = <missing> -> 100
CHANGED: debug = True -> False
REMOVED: name = 'app' -> <missing>
ADDED: version = <missing> -> '2.0'
How it works
The function recursively walks both dictionaries by taking the union of keys from the two inputs. For each key, it checks if the key is only in one dict (added or removed), or if both values are dictionaries it recurses deeper, otherwise it compares scalar values and records a change. The path parameter builds a dotted key path like server.port so differences are easy to locate. Because the iteration order follows the set union, results may appear in a different order but will always cover every difference.
Common mistakes
- Forgetting to recurse when both values are dicts, so nested differences are missed.
- Assuming keys in both dicts are identical, so changes inside nested dicts are overlooked.
- Using `a.keys() | b.keys()` on older Python versions that don't support dict union or set-like views (needs 3.9+).
- Not accounting for differences in type (e.g., list vs dict) which the function treats as a simple change.
Variations
- Use the `deepdiff` third-party package (pip install deepdiff) to get richer diff output with ignore-order options.
- Return a dict of change types instead of a list of tuples, or use a dataclass for typed diff records.
Real-world use cases
- Compare configuration files across environments (dev vs prod) to spot drift before deployment.
- Debug why an application behaves differently by comparing runtime settings from two service instances.
- Check for unexpected changes in a schema or feature flag state when rolling out a new release.
Sponsored
More from Errors & debugging
- Catch RecursionError and Fail Gracefully in Python easy
- Catch ValueError and print friendly message in Python easy
- Collect Multiple Validation Errors in Python Before Raising medium
- Handle ValueError and ZeroDivisionError in Python with try except easy
- How to Add a Correlation ID to Logging Records in Python medium
- How to Assert Preconditions with Descriptive Messages in Python easy
Keep learning
Related tutorials and quizzes for this topic.