Default Values with collections.defaultdict
15 min · Beginner
Need help? Ask a tutor on WhatsApp (opens in a new tab)When grouping items into lists or tallying scores with a standard dictionary, you must repeatedly check if a key exists before appending or adding. collections.defaultdict eliminates this boilerplate by automatically calling a factory function whenever a missing key is accessed.
The Grouping Problem with Standard dict
With a standard dictionary, grouping elements requires defensive checks.
# Boilerplate approach:groups = {}for name, dept in [("Aarav", "Eng"), ("Diya", "Eng"), ("Arjun", "HR")]: if dept not in groups: groups[dept] = [] groups[dept].append(name)Every loop iteration performs redundant key checks.
The defaultdict Solution
defaultdict is a dictionary that calls a factory function to create a value when a missing key is read, then stores it under that key.
Pass a callable (the default_factory) to defaultdict. If a key does not exist, the factory is called automatically to create a new value.
from collections import defaultdict
groups = defaultdict(list)for name, dept in [("Aarav", "Eng"), ("Diya", "Eng"), ("Arjun", "HR")]: groups[dept].append(name) # Clean & automatic!
print(dict(groups))Example 1 · Group orders by category
from collections import defaultdict
orders = [ ("Electronics", "Laptop"), ("Clothing", "Jacket"), ("Electronics", "Headphones"), ("Books", "Clean Code")]
categories = defaultdict(list)for cat, item in orders: categories[cat].append(item)
for cat, items in categories.items(): print(f"{cat}: {items}")Electronics: ['Laptop', 'Headphones'] Clothing: ['Jacket'] Books: ['Clean Code']
What this code does
defaultdict(list)groups items by department cleanly.- Zero
if key not in dboilerplate.
Example 2 · Unique buyers per product
from collections import defaultdict
purchases = [("Laptop", "Aarav"), ("Mouse", "Diya"), ("Laptop", "Aarav"), ("Laptop", "Diya")]buyers = defaultdict(set)for product, customer in purchases: buyers[product].add(customer)
for product, names in buyers.items(): print(product, len(names))Laptop 2 Mouse 1
What this code does
- A set ignores duplicates, so Aarav counts once for the Laptop.
defaultdict(set)creates the empty set on the first purchase of each product.
Accessing groups["Eng"] automatically calls list() to produce an empty list on first access.
Common Default Factories
Overview of Common Default Factories:
defaultdict(list): For grouping records by key into lists.defaultdict(set): For grouping items with automatic duplicate removal.defaultdict(int): For numeric counters (callsint(), defaulting to 0).defaultdict(lambda: "Guest"): Custom default strings or configurations.
Building Graph Adjacency Lists
Adjacency list is a graph stored as a dictionary that maps each node to the list of nodes it connects to.
In DSA graph algorithms, defaultdict(list) represents directed and undirected graphs effortlessly.
edges = [("A", "B"), ("A", "C"), ("B", "D")]graph = defaultdict(list)for u, v in edges: graph[u].append(v)
print("A neighbors:", graph["A"])print("Z neighbors (new node):", graph["Z"]) # Returns empty []!Example 3 · Two-way friend links
from collections import defaultdict
links = [("A", "B"), ("A", "C"), ("B", "C"), ("D", "A")]graph = defaultdict(set)for u, v in links: graph[u].add(v) graph[v].add(u)
for node in sorted(graph): print(node, sorted(graph[node]))A ['B', 'C', 'D'] B ['A', 'C'] C ['A', 'B'] D ['A']
What this code does
- Each link is stored in both directions, so the graph is undirected.
- Node D has no entry of its own in
links, butgraph[v]creates it when A is added.
Example 4 · Sales by region and product
from collections import defaultdict
rows = [("North", "Laptop", 2), ("North", "Mouse", 5), ("South", "Laptop", 1), ("North", "Laptop", 3)]sales = defaultdict(lambda: defaultdict(int))for region, product, units in rows: sales[region][product] += units
print({region: dict(items) for region, items in sales.items()}){'North': {'Laptop': 5, 'Mouse': 5}, 'South': {'Laptop': 1}}What this code does
- The outer factory builds a new inner
defaultdict(int)for each region. - The final comprehension turns the result into plain dicts for printing.
No KeyError can ever occur when traversing graph nodes.
The Lookup Trap: Reading Also Writes
Reading a missing key inserts it. Use in or get() to look without changing the dictionary, and set default_factory to None to switch the feature off.
from collections import defaultdict
stock = defaultdict(int)stock["laptop"] = 4
print(stock.get("phone"), len(stock))print(stock["phone"], len(stock))print("tablet" in stock, len(stock))
stock.default_factory = Nonetry: stock["tablet"]except KeyError: print("KeyError now")Example 5 · A silent insert in a report
from collections import defaultdict
scores = defaultdict(int, {"Aarav": 90})for name in ["Aarav", "Diya"]: if scores[name] < 50: print(name, "needs support")print(sorted(scores))Diya needs support ['Aarav', 'Diya']
What this code does
- Checking
scores["Diya"]created the key with the value 0, so Diya is now listed. - Use
scores.get(name, 0)when you only want to look.
Once a report is built, freeze it with default_factory = None so a typo raises an error instead of adding a key.
Next step
Check your understanding
11 quick questions