One of the most common patterns in Python is building a dictionary where each key maps to a list or a count. You have probably written something like this dozens of times:
groups = {}
for item in data:
if item['category'] not in groups:
groups[item['category']] = []
groups[item['category']].append(item)
It works, but the key existence check is boilerplate you have to repeat everywhere. The defaultdict from Python's collections module eliminates it entirely.
What is a defaultdict?
A defaultdict is a subclass of the built-in dict. The difference is that it accepts a callable when you create it. Whenever you access a key that does not exist, instead of raising a KeyError, it calls that callable and uses the result as the default value for the new key.
from collections import defaultdict
groups = defaultdict(list)
for item in data:
groups[item['category']].append(item)
The list callable is called with no arguments, producing an empty list [], which is then stored and returned. Your loop shrinks to two lines.
Common use cases
Counting occurrences:
from collections import defaultdict
word_count = defaultdict(int)
for word in text.split():
word_count[word] += 1
When you access a missing key, int() returns 0, so incrementing always works without an initialization step.
Grouping items:
from collections import defaultdict
employees_by_dept = defaultdict(list)
for emp in employees:
employees_by_dept[emp['department']].append(emp['name'])
Building a graph as an adjacency list:
from collections import defaultdict
graph = defaultdict(set)
for u, v in edges:
graph[u].add(v)
graph[v].add(u)
Using a lambda for custom defaults
The callable does not have to be a built-in type. You can pass a lambda to produce any default value you want:
from collections import defaultdict
config = defaultdict(lambda: 'N/A')
config['host'] = 'localhost'
print(config['host']) # localhost
print(config['timeout']) # N/A
Nested defaultdicts
For multi-level grouping, you can nest them:
from collections import defaultdict
nested = defaultdict(lambda: defaultdict(int))
nested['python']['functions'] += 1
nested['python']['classes'] += 3
nested['javascript']['functions'] += 5
print(nested['python']) # defaultdict(int, {'functions': 1, 'classes': 3})
One thing to watch out for
Because defaultdict creates a key on first access, checking for the presence of a key with if key in d is safe — it does not trigger the default. But accessing d[key] directly will create the key as a side effect. If you need to check without creating, use d.get(key) or key in d rather than direct access.
When to use it
Reach for defaultdict whenever your first access to a key should produce a predictable starting value. Counters, grouped collections, adjacency lists, and frequency maps are all natural fits. For simple single-value defaults where you do not want the key created automatically, dict.get(key, default) or dict.setdefault may be cleaner. But for accumulation patterns, defaultdict is the right tool.