Table of Contents

    defaultdict

    PYTHON & DATA STRUCTURES

    Python defaultdict — The Complete Guide

    Master collections.defaultdict to eliminate KeyError, auto-initialize values, and write cleaner grouping and counting code.

    Introduction

    defaultdict is a dictionary subclass from Python's built-in collections module that automatically creates a default value for any key that doesn't exist yet. Instead of checking whether a key is present before using it, you just access it — and defaultdict supplies a sensible default (an empty list, zero, an empty set, etc.) on the fly. This removes a whole class of KeyError bugs and makes grouping, counting, and accumulating data far cleaner.

    In one line: defaultdict is a dictionary that creates a default value automatically whenever you touch a missing key.

    Real-World Analogy

    The Self-Filling Mailboxes

    Imagine an apartment building where the moment a new tenant's name is mentioned, an empty mailbox instantly appears for them — no paperwork, no "does this mailbox exist?" check. defaultdict works the same way: mention a new key and its empty container is ready and waiting.

    Prerequisites

    Before You Start

    • Python 3.x installed
    • Understanding of dictionaries and key/value access
    • The module is built in — just from collections import defaultdict
    • Familiarity with lists, sets, and integers as values
    • Awareness of what a KeyError is and when it occurs

    The Problem It Solves

    With a normal dict, accessing a missing key raises an error, forcing defensive checks.

    # Plain dict — verbose and error-prone
    d = {}
    
    # This raises KeyError:
    # d["fruits"].append("apple")
    
    # You must check first:
    if "fruits" not in d:
        d["fruits"] = []
    d["fruits"].append("apple")
    print(d)   # {'fruits': ['apple']}
    With defaultdict The check disappears entirely — the empty list is created automatically.
    from collections import defaultdict
    
    d = defaultdict(list)
    d["fruits"].append("apple")   # no check needed!
    print(d)   # defaultdict(<class 'list'>, {'fruits': ['apple']})

    How It Works: default_factory

    You pass a factory function to defaultdict. When a missing key is accessed, it calls that function with no arguments to produce the default value, stores it, and returns it.

    CORE CONCEPT
    Missing key calls default_factory()

    Common Default Factories

    Factory Default Value Typical Use
    int 0 Counting occurrences
    list [] Grouping items into lists
    set set() Collecting unique items
    dict {} Building nested dictionaries
    float 0.0 Summing/averaging
    lambda: X Custom value X Any custom default

    Counting with defaultdict(int)

    Each new key starts at 0, so you can increment immediately.

    from collections import defaultdict
    
    text = "banana"
    counts = defaultdict(int)
    
    for char in text:
        counts[char] += 1        # missing keys start at 0
    
    print(dict(counts))   # {'b': 1, 'a': 3, 'n': 2}

    Grouping with defaultdict(list)

    The most popular use — group items under a key without pre-creating lists.

    from collections import defaultdict
    
    people = [
        ("Engineering", "Alice"),
        ("Sales", "Bob"),
        ("Engineering", "Charlie"),
        ("Sales", "Dave"),
    ]
    
    teams = defaultdict(list)
    for dept, name in people:
        teams[dept].append(name)
    
    print(dict(teams))
    # {'Engineering': ['Alice', 'Charlie'], 'Sales': ['Bob', 'Dave']}

    Unique Collection with defaultdict(set)

    from collections import defaultdict
    
    edges = [("A", "B"), ("A", "B"), ("A", "C"), ("B", "C")]
    
    graph = defaultdict(set)
    for src, dst in edges:
        graph[src].add(dst)      # duplicates ignored automatically
    
    print(dict(graph))
    # {'A': {'B', 'C'}, 'B': {'C'}}

    Nested defaultdict

    Use a lambda to build multi-level structures without manual initialization.

    from collections import defaultdict
    
    # A dict of dicts of ints
    matrix = defaultdict(lambda: defaultdict(int))
    
    matrix["row1"]["col1"] += 5
    matrix["row1"]["col2"] += 3
    matrix["row2"]["col1"] += 7
    
    print(matrix["row1"]["col1"])   # 5
    print(matrix["row2"]["col2"])   # 0  (auto-created)
    Watch out Merely accessing a missing key creates it. Reading matrix["row3"]["col9"] silently adds those entries.

    defaultdict vs dict.setdefault()

    Both avoid KeyError, but they differ in style and repetition.

    from collections import defaultdict
    
    # Using dict.setdefault (works, but repeats the default each time)
    d1 = {}
    d1.setdefault("a", []).append(1)
    d1.setdefault("a", []).append(2)
    
    # Using defaultdict (cleaner for repeated use)
    d2 = defaultdict(list)
    d2["a"].append(1)
    d2["a"].append(2)
    
    print(d1)          # {'a': [1, 2]}
    print(dict(d2))    # {'a': [1, 2]}
    Aspect defaultdict dict + setdefault
    Missing key Auto-creates default Must pass default each call
    Readability Cleaner for repeated access Verbose if reused
    Side effect Access creates the key Only setdefault creates it
    Best for Grouping/counting loops One-off defaults

    Reading Without Creating Keys

    To check a value without accidentally creating it, use .get() or in.

    from collections import defaultdict
    
    d = defaultdict(int)
    print(d.get("x", 0))    # 0 — does NOT create key "x"
    print("x" in d)         # False — still not created
    print(d["x"])           # 0 — THIS creates key "x"
    print("x" in d)         # True

    Performance

    Like a regular dict, defaultdict offers average \(O(1)\) access, insertion, and lookup. Building a grouped structure from \(n\) items is a single pass:

    \[ T_{\text{group}} = O(n) \]

    The auto-default adds no meaningful overhead over manual checks — you get cleaner code for free.

    Best Practices

    Do This

    • Use defaultdict(list) for grouping and defaultdict(int) for counting
    • Use .get() or in to check keys without creating them
    • Use a lambda for nested or custom default values
    • Convert to a plain dict() before returning from a function or serializing
    • Set d.default_factory = None to restore KeyError behaviour if needed
    • Prefer Counter when pure counting with ranking is the goal

    Common Mistakes

    Bad Passing a value instead of a callable: defaultdict([]) — this raises a TypeError.
    Good Pass the factory: defaultdict(list) — note no parentheses after list.
    Bad Accessing keys just to test them — d[key] silently creates and stores an empty default.
    Good Use key in d or d.get(key) when you only want to read.

    Interview Questions

    Question Short Answer
    What is a defaultdict? A dict subclass that auto-creates a default value for missing keys via a factory.
    What is default_factory? The callable used to produce defaults (e.g. int, list).
    defaultdict vs setdefault? defaultdict auto-defaults on access; setdefault needs the default every call.
    Does reading a missing key create it? Yes — use get() or in to avoid that.
    How to make nested defaultdicts? Use a lambda: defaultdict(lambda: defaultdict(int)).

    Quick Revision

    Goal Code
    Count items defaultdict(int)
    Group into lists defaultdict(list)
    Collect unique defaultdict(set)
    Nested structure defaultdict(lambda: defaultdict(int))
    Read safely d.get(key, default)

    Key Takeaways

    defaultdict eliminates KeyError by auto-creating defaults from a factory function. Use int to count, list to group, set for uniqueness, and a lambda for nesting — just remember that accessing a missing key creates it.