How to Compare Lists of Dictionaries in Python
Comparing lists of dictionaries is a common task in Python, whether you're validating API responses, checking for data changes between snapshots, synchronizing records, or writing tests. The comparison can range from a simple equality check to more nuanced comparisons based on specific keys, order sensitivity, or custom logic.
In this guide, you'll learn multiple methods to compare lists of dictionaries in Python, from straightforward approaches to advanced techniques that handle real-world complexity.
Direct Equality Comparison
The simplest way to compare two lists of dictionaries is using the == operator. This checks if both lists have the same dictionaries in the same order.
a = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
b = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
c = [{'id': 2, 'name': 'Bob'}, {'id': 1, 'name': 'Alice'}]
print(a == b) # Same content, same order
print(a == c) # Same content, different order
Output:
True
False
Direct == comparison is order-sensitive. Lists a and c contain identical dictionaries, but the comparison returns False because they appear in a different order. If order doesn't matter, use one of the order-independent methods below.
Order-Independent Comparison Using Sets
When the order of dictionaries doesn't matter and you only care that both lists contain the same items, convert the dictionaries to frozenset objects so they can be placed into sets for comparison.
a = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
b = [{'id': 2, 'name': 'Bob'}, {'id': 1, 'name': 'Alice'}]
set_a = {frozenset(d.items()) for d in a}
set_b = {frozenset(d.items()) for d in b}
print("Identical" if set_a == set_b else "Not identical")
Output:
Identical
How it works:
- Each dictionary is converted to a
frozensetof its key-value pairs, making it hashable. - The lists become sets of frozensets, which are compared regardless of order.
This method only works when dictionary values are hashable (strings, numbers, tuples, etc.). If your dictionaries contain lists or nested dictionaries as values, frozenset(d.items()) will raise a TypeError:
d = {'id': 1, 'tags': ['python', 'data']}
frozenset(d.items()) # TypeError: unhashable type: 'list'
For nested structures, use the json.dumps() approach shown later in this guide.
Finding Common Dictionaries Between Lists
To identify which dictionaries appear in both lists, use a list comprehension:
a = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}, {'id': 3, 'name': 'Charlie'}]
b = [{'id': 2, 'name': 'Bob'}, {'id': 4, 'name': 'David'}, {'id': 3, 'name': 'Charlie'}]
common = [d for d in a if d in b]
print(f"Common items: {common}")
Output:
Common items: [{'id': 2, 'name': 'Bob'}, {'id': 3, 'name': 'Charlie'}]
Finding Differences Between Lists
You can also find items that exist in one list but not the other:
a = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
b = [{'id': 2, 'name': 'Bob'}, {'id': 3, 'name': 'Charlie'}]
only_in_a = [d for d in a if d not in b]
only_in_b = [d for d in b if d not in a]
print(f"Only in a: {only_in_a}")
print(f"Only in b: {only_in_b}")
Output:
Only in a: [{'id': 1, 'name': 'Alice'}]
Only in b: [{'id': 3, 'name': 'Charlie'}]
Comparing Based on Specific Keys
Sometimes you don't need full dictionary equality: you only care about matching on certain keys like id. Extract the relevant values and compare:
a = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
b = [{'id': 2, 'name': 'Robert'}, {'id': 3, 'name': 'Charlie'}]
ids_a = {item['id'] for item in a}
ids_b = {item['id'] for item in b}
common_ids = ids_a & ids_b # Intersection
only_in_a = ids_a - ids_b # In a but not b
only_in_b = ids_b - ids_a # In b but not a
print(f"Common IDs: {common_ids}")
print(f"Only in a: {only_in_a}")
print(f"Only in b: {only_in_b}")
Output:
Common IDs: {2}
Only in a: {1}
Only in b: {3}
Detecting Value Changes for Matching Keys
A more useful pattern is finding records that share the same key but have different values, effectively detecting updates:
a = [{'id': 1, 'name': 'Alice', 'age': 30}, {'id': 2, 'name': 'Bob', 'age': 25}]
b = [{'id': 1, 'name': 'Alice', 'age': 31}, {'id': 2, 'name': 'Bob', 'age': 25}]
# Index by 'id' for easy lookup
dict_a = {item['id']: item for item in a}
dict_b = {item['id']: item for item in b}
for key in dict_a:
if key in dict_b:
if dict_a[key] != dict_b[key]:
print(f"ID {key} changed:")
for field in dict_a[key]:
if dict_a[key][field] != dict_b[key].get(field):
print(f" {field}: {dict_a[key][field]} → {dict_b[key][field]}")
else:
print(f"ID {key}: unchanged")
Output:
ID 1 changed:
age: 30 → 31
ID 2: unchanged
Custom Comparison Logic
For complex scenarios, such as case-insensitive string matching or tolerance-based numeric comparisons, define a custom comparison function:
def records_match(d1, d2):
"""Compare dictionaries with case-insensitive name matching."""
return (
d1['id'] == d2['id'] and
d1['name'].lower() == d2['name'].lower()
)
a = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'BOB'}]
b = [{'id': 1, 'name': 'ALICE'}, {'id': 3, 'name': 'Charlie'}]
matches = [
(d1, d2) for d1 in a for d2 in b
if records_match(d1, d2)
]
print("Matches found:")
for d1, d2 in matches:
print(f" {d1} ↔ {d2}")
Output:
Matches found:
{'id': 1, 'name': 'Alice'} ↔ {'id': 1, 'name': 'ALICE'}
Handling Nested Dictionaries
When dictionaries contain nested structures like lists or other dictionaries, the frozenset approach won't work. Instead, serialize each dictionary to a sorted JSON string for comparison:
import json
def normalize(d):
"""Convert a dictionary to a sorted JSON string for comparison."""
return json.dumps(d, sort_keys=True)
a = [
{'id': 1, 'tags': ['python', 'data'], 'meta': {'active': True}},
{'id': 2, 'tags': ['java'], 'meta': {'active': False}}
]
b = [
{'id': 2, 'meta': {'active': False}, 'tags': ['java']},
{'id': 1, 'meta': {'active': True}, 'tags': ['python', 'data']}
]
set_a = {normalize(d) for d in a}
set_b = {normalize(d) for d in b}
print("Identical" if set_a == set_b else "Not identical")
Output:
Identical
json.dumps(d, sort_keys=True) normalizes key order at all nesting levels, producing consistent strings for comparison. This handles nested dicts but note that list order still matters , ['python', 'data'] and ['data', 'python'] will produce different strings.
Building a Comprehensive Diff Function
For production use, here's a reusable function that provides a complete comparison report:
def compare_dict_lists(list_a, list_b, key_field='id'):
"""
Compare two lists of dictionaries using a key field.
Returns added, removed, changed, and unchanged records.
"""
index_a = {item[key_field]: item for item in list_a}
index_b = {item[key_field]: item for item in list_b}
keys_a = set(index_a.keys())
keys_b = set(index_b.keys())
added = [index_b[k] for k in keys_b - keys_a]
removed = [index_a[k] for k in keys_a - keys_b]
changed = [
{'old': index_a[k], 'new': index_b[k]}
for k in keys_a & keys_b if index_a[k] != index_b[k]
]
unchanged = [index_a[k] for k in keys_a & keys_b if index_a[k] == index_b[k]]
return {
'added': added,
'removed': removed,
'changed': changed,
'unchanged': unchanged
}
# Example usage
old_data = [
{'id': 1, 'name': 'Alice', 'role': 'Developer'},
{'id': 2, 'name': 'Bob', 'role': 'Designer'},
{'id': 3, 'name': 'Charlie', 'role': 'Manager'}
]
new_data = [
{'id': 1, 'name': 'Alice', 'role': 'Senior Developer'},
{'id': 3, 'name': 'Charlie', 'role': 'Manager'},
{'id': 4, 'name': 'David', 'role': 'Intern'}
]
result = compare_dict_lists(old_data, new_data)
print(f"Added: {result['added']}")
print(f"Removed: {result['removed']}")
print(f"Changed: {result['changed']}")
print(f"Unchanged: {result['unchanged']}")
Output:
Added: [{'id': 4, 'name': 'David', 'role': 'Intern'}]
Removed: [{'id': 2, 'name': 'Bob', 'role': 'Designer'}]
Changed: [{'old': {'id': 1, 'name': 'Alice', 'role': 'Developer'}, 'new': {'id': 1, 'name': 'Alice', 'role': 'Senior Developer'}}]
Unchanged: [{'id': 3, 'name': 'Charlie', 'role': 'Manager'}]
Quick Comparison of Methods
| Method | Order Sensitive | Handles Nested | Shows Differences | Best For |
|---|---|---|---|---|
== operator | ✅ Yes | ✅ Yes | ❌ | Simple, ordered equality check |
frozenset + sets | ❌ No | ❌ No | ❌ | Order-independent, flat dicts |
json.dumps(sort_keys=True) | ❌ No | ✅ Yes | ❌ | Order-independent, nested dicts |
| Key-based comparison | N/A | ✅ Yes | ✅ | Matching records by identifier |
| Custom comparison function | Configurable | ✅ Yes | ✅ | Complex business logic |
Conclusion
Comparing lists of dictionaries in Python can be approached in several ways depending on your requirements:
- Use
==for a quick, order-sensitive equality check when both structure and sequence must match. - Use
frozensetconversion for order-independent comparison of flat dictionaries. - Use
json.dumps(sort_keys=True)when dictionaries contain nested structures that need order-independent comparison. - Use key-based indexing when you need to match records by a unique identifier and detect additions, removals, and changes.
- Use custom comparison functions for domain-specific logic like case-insensitive matching or tolerance-based numeric comparisons.
For most real-world scenarios, such as syncing data or tracking changes, the key-based diff approach provides the most actionable results by categorizing records into added, removed, changed, and unchanged groups.