Avoid Python KeyError with defaultdict

Updated on: May 28, 2026
Reading time: 7 minutes
Como evitar KeyError usando defaultdict em Python

A KeyError is one of the most common errors Python developers face when working with dictionaries. It happens when you try to access a key that does not exist. For small scripts, you can often fix it with a quick if check or a try block. But when your code repeatedly creates groups, counters, lists, or nested mappings, those checks can make the code noisy. That is where defaultdict becomes useful.

This English version is adapted for readers who want a practical guide, not a literal translation. You will learn what causes KeyError, how defaultdict works, what default_factory means, when to use int, list, set, or custom factories, how defaultdict compares with dict.get(), setdefault(), and Counter, and which mistakes to avoid. If you are still learning dictionaries, start with this guide to Python dictionaries and this broader overview of Python data types.

What Is KeyError in Python?

KeyError is raised when a mapping object, usually a dictionary, cannot find the requested key. A dictionary stores values by key. If the key exists, Python returns the value. If the key does not exist and you use square-bracket access, Python raises an exception. This is intentional because it prevents missing data from being ignored silently.

user = {"name": "Ana"}

print(user["name"])
print(user["age"])  # KeyError

In this example, name exists, but age does not. Python cannot invent a value, so it raises KeyError. The official Python exception documentation defines KeyError as the error raised when a mapping key is not found.

Why KeyError Happens So Often

Dictionaries are flexible, but that flexibility makes missing keys common. You may receive incomplete JSON from an API, parse inconsistent CSV rows, count items that have not appeared yet, group records by a category, or build nested structures dynamically. In all these cases, your code may ask for a key before it exists.

For example, counting words with a normal dictionary requires an initialization step. Before you increment a count, the key must already exist. If it does not, counts[word] += 1 fails because Python must first read counts[word] before adding one. If the key is missing, the read operation raises KeyError.

counts = {}

for word in ["python", "code", "python"]:
    counts[word] += 1  # KeyError on the first word

You can solve this manually, but repeated manual checks can make code harder to read. If you are learning error handling first, this guide to try and except in Python explains how exceptions work in general.

What Is defaultdict?

defaultdict is a dictionary subclass from the collections module. It behaves like a normal dictionary, but it has one extra feature: when a missing key is accessed, it can automatically create a default value for that key. The function that creates that default value is called default_factory.

The official Python collections documentation describes defaultdict as a dict subclass that calls a factory function to supply missing values. In practice, this means you can write cleaner code for counters, grouped lists, grouped sets, and nested structures.

Counting Items with defaultdict(int)

The most common beginner-friendly use is counting. If you pass int as the factory, every missing key starts with 0. That works because calling int() with no arguments returns zero.

from collections import defaultdict

counts = defaultdict(int)

words = ["python", "code", "python", "data", "code"]

for word in words:
    counts[word] += 1

print(dict(counts))

When the loop sees "python" for the first time, counts["python"] does not exist. Instead of raising KeyError, the defaultdict calls int(), creates the value 0, stores it under the key, and then increments it. This removes the need for if word not in counts.

Grouping Values with defaultdict(list)

Another common use is grouping. If you pass list as the factory, each missing key starts with an empty list. That makes it easy to group records by category, user, status, date, or any other key.

from collections import defaultdict

groups = defaultdict(list)

students = [
    {"name": "Ana", "class": "A"},
    {"name": "Bruno", "class": "B"},
    {"name": "Carla", "class": "A"},
]

for student in students:
    groups[student["class"]].append(student["name"])

print(dict(groups))

Without defaultdict, you would need to check whether each class already exists before appending. With defaultdict(list), the first access creates an empty list automatically. This pattern is useful in reports, data cleaning, logs, and API processing. If you work often with lists, review this guide to Python lists.

Collecting Unique Values with defaultdict(set)

If you want unique grouped values, use set as the factory. A set stores unique items, so duplicate values are ignored automatically. This is helpful when grouping tags, permissions, visited pages, product IDs, or users by category.

from collections import defaultdict

permissions = defaultdict(set)

rows = [
    ("admin", "read"),
    ("admin", "write"),
    ("admin", "read"),
    ("guest", "read"),
]

for role, permission in rows:
    permissions[role].add(permission)

print({role: sorted(values) for role, values in permissions.items()})

This gives each role a set of permissions without manual initialization. For comparison between common containers, see this article on the difference between lists, tuples, sets, and dictionaries.

default_factory Explained

The first argument passed to defaultdict is the default factory. It must be callable, meaning Python must be able to call it like a function. Common factories are int, list, set, and dict. You can also use a custom function when you need a specific default value.

from collections import defaultdict

def unknown_score():
    return "unknown"

scores = defaultdict(unknown_score)
print(scores["Ana"])

Do not pass the result of a function call unless that result is itself callable. For example, defaultdict(list) is correct, but defaultdict(list()) is wrong because list() creates a list immediately. The factory should be the function itself, not a value created by calling the function.

defaultdict vs dict.get()

dict.get() is useful when you want to read a value safely without modifying the dictionary. It returns a default value if the key is missing, but it does not insert that key into the dictionary. This is different from defaultdict, which inserts a default value when a missing key is accessed with square brackets.

user = {"name": "Ana"}

print(user.get("age", 0))
print(user)

Use get() when you only need a fallback for reading. Use defaultdict when missing keys should become real entries because you are building a structure over time. This distinction matters in data processing because accidentally creating keys may change your output.

defaultdict vs setdefault()

setdefault() is another way to initialize missing keys. It works on normal dictionaries and returns the existing value if the key exists, or inserts a default if the key is missing. It can be useful, but it often becomes less readable when repeated many times.

groups = {}

for name, class_name in [("Ana", "A"), ("Bruno", "B"), ("Carla", "A")]:
    groups.setdefault(class_name, []).append(name)

print(groups)

This is valid Python. However, defaultdict(list) expresses the intention more clearly when every missing key should start as a list. Use setdefault() for occasional initialization and defaultdict for repeated grouping or accumulating.

defaultdict vs Counter

If your only goal is counting hashable items, Counter may be even clearer than defaultdict(int). Counter also lives in the collections module and is designed specifically for counting. It provides helpful methods such as most_common().

from collections import Counter

words = ["python", "code", "python", "data", "code"]
counts = Counter(words)

print(counts.most_common())

Use Counter for pure counting. Use defaultdict(int) when counting is only part of a larger custom structure or when you need more control over how values are updated. If you are learning loops at the same time, this guide to for loops in Python will help you understand how these patterns run step by step.

Nested defaultdict

You can create nested dictionaries with defaultdict, but you should be careful. Nested factories can make code concise, but they can also become hard to understand if you go too deep. A common pattern is a dictionary where each missing key creates another dictionary or another defaultdict.

from collections import defaultdict

sales = defaultdict(lambda: defaultdict(int))

sales["January"]["books"] += 3
sales["January"]["courses"] += 1
sales["February"]["books"] += 2

print({month: dict(items) for month, items in sales.items()})

This creates a two-level structure where each month contains counters for categories. It is powerful, but if the structure becomes more complex, consider using classes, dataclasses, or explicit helper functions. Readability should still come first.

Common Mistakes with defaultdict

The first mistake is forgetting that accessing a missing key creates it. If you only wanted to check whether a key exists, use in or get() instead. The second mistake is passing a value instead of a factory, such as defaultdict([]). The third mistake is using defaultdict where missing keys should actually be treated as errors.

That last point is important. defaultdict is not a universal replacement for normal dictionaries. Sometimes a missing key reveals bad input, a broken assumption, or a real bug. In those cases, allowing KeyError may be better than hiding the problem. If your code should fail when data is missing, use a normal dictionary and handle the error intentionally.

When Should You Use defaultdict?

Use defaultdict when your code naturally builds values for keys over time. Good examples include counting, grouping, collecting sets, accumulating totals, building indexes, and creating nested structures. It is especially helpful when the default value is always the same kind of object, such as zero, an empty list, or an empty set.

A simple rule works well: if you repeatedly write if key not in dictionary only to initialize a value, consider defaultdict. If you only need a fallback once or twice, dict.get() or setdefault() may be enough. If a missing key indicates invalid data, keep the normal dictionary behavior and let the error be visible.

Final Checklist

KeyError happens when you access a missing dictionary key with square brackets. defaultdict avoids this by creating default values for missing keys. Use defaultdict(int) for counters, defaultdict(list) for grouping, and defaultdict(set) for unique grouped values. Use get() when you do not want to insert missing keys. Use Counter when you only need counting.

The real value of defaultdict is not just avoiding errors. It helps you express intent. Instead of writing repetitive initialization logic, you tell Python what the default should be and focus on the transformation you actually want. Used carefully, it makes dictionary-heavy code shorter, clearer, and easier to maintain.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Introdução ao módulo itertools para iniciantes em Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python itertools: Practical Beginner Guide

    Learn Python itertools with practical examples of count, cycle, repeat, chain, combinations, permutations, groupby, tee, and memory-efficient iteration.

    Ler mais

    Tempo de leitura: 8 minutos
    28/05/2026
    Instalação offline de pacotes Python sem internet
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    How to Install Python Packages Offline in Minutes

    Learn how to install Python packages offline with pip download, pip install --no-index, wheels, cross-platform files, Conda, and private mirrors.

    Ler mais

    Tempo de leitura: 9 minutos
    19/05/2026
    Copiando e movendo arquivos com Python usando shutil
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Copy and Move Files with Python shutil

    Learn how to copy and move files with Python shutil using copy, copy2, move, copytree, rmtree, error handling, and an

    Ler mais

    Tempo de leitura: 6 minutos
    19/05/2026
    Compactando arquivos ZIP automaticamente com Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    How to Create ZIP Files with Python in 2 Minutes

    Learn how to create ZIP files in Python with zipfile, compress folders, filter extensions, handle errors, and build a ready-to-run

    Ler mais

    Tempo de leitura: 7 minutos
    19/05/2026
    Como acelerar código Python usando lru cache
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Speed Up Python Code with @lru_cache

    Learn how @lru_cache speeds up Python code with memoization, maxsize, cache_info, cache_clear, API caching, and cases where it should be

    Ler mais

    Tempo de leitura: 7 minutos
    19/05/2026
    Monitoramento de pastas em tempo real com Python Watchdog
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Monitor Folders in Real Time with Python Watchdog

    Learn how to monitor folders in real time with Python Watchdog using Observer, handlers, file filters, permissions, logging, and a

    Ler mais

    Tempo de leitura: 7 minutos
    19/05/2026