Using Default Dictionaries

Stop writing if-key-not-in-dict checks forever

Posted by Syed Zain Raza

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.