Skip to content
Home Python defaultdict and OrderedDict in Python

defaultdict and OrderedDict in Python

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Data Structures → Topic 12 of 12
Python's defaultdict and OrderedDict from collections — when to use defaultdict to avoid KeyError, factory functions, and whether OrderedDict is still relevant in Python 3.
⚙️ Intermediate — basic Python knowledge assumed
In this tutorial, you'll learn
Python's defaultdict and OrderedDict from collections — when to use defaultdict to avoid KeyError, factory functions, and whether OrderedDict is still relevant in Python 3.
  • defaultdict(list) eliminates the if-key-not-in-dict pattern for grouping operations.
  • The factory function (list, int, set, or a lambda) is called with no arguments when a missing key is accessed.
  • Accessing a missing key in defaultdict creates it — be aware of this when iterating.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

defaultdict automatically creates a default value when you access a missing key — no more checking 'if key in dict' before appending. Pass the factory function: defaultdict(list) creates an empty list for missing keys. OrderedDict preserves insertion order (as does regular dict since Python 3.7) and adds move_to_end() and a meaningful __eq__ that considers order.

defaultdict — No More KeyError

Example · PYTHON
12345678910111213141516171819202122232425
from collections import defaultdict

# Group words by their first letter
words = ['apple', 'avocado', 'banana', 'blueberry', 'cherry', 'apricot']

# Without defaultdict — verbose
groups = {}
for word in words:
    if word[0] not in groups:   # check before every append
        groups[word[0]] = []
    groups[word[0]].append(word)

# With defaultdict — clean
groups = defaultdict(list)     # list() called for every new key
for word in words:
    groups[word[0]].append(word)  # KeyError impossible

print(dict(groups))
# {'a': ['apple', 'avocado', 'apricot'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

# Count occurrences
counts = defaultdict(int)     # int() returns 0
for word in words:
    counts[word[0]] += 1
print(dict(counts))  # {'a': 3, 'b': 2, 'c': 1}
▶ Output
{'a': ['apple', 'avocado', 'apricot'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
{'a': 3, 'b': 2, 'c': 1}

defaultdict with Custom Factories

Example · PYTHON
123456789101112131415161718192021
from collections import defaultdict

# Any callable works as a factory
dd_set   = defaultdict(set)        # empty set for missing keys
dd_zero  = defaultdict(lambda: 0)  # 0 for missing keys
dd_const = defaultdict(lambda: 'N/A')  # string constant

# Nested defaultdict — for 2-level grouping
transactions = [
    ('2026-03-01', 'Engineering', 500),
    ('2026-03-01', 'Marketing',   300),
    ('2026-03-02', 'Engineering', 700),
]

# Date → Department → total
monthly = defaultdict(lambda: defaultdict(int))
for date, dept, amount in transactions:
    monthly[date][dept] += amount

print(dict(monthly['2026-03-01']))
# {'Engineering': 500, 'Marketing': 300}
▶ Output
{'Engineering': 500, 'Marketing': 300}

OrderedDict — When It Still Matters

Example · PYTHON
12345678910111213141516171819202122232425
from collections import OrderedDict

# Since Python 3.7, regular dicts maintain insertion order
# So when is OrderedDict useful?

# 1. Order-sensitive equality
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
regular1 = {'a': 1, 'b': 2}
regular2 = {'b': 2, 'a': 1}

print(od1 == od2)       # False — order matters for OrderedDict
print(regular1 == regular2)  # True — regular dict ignores order

# 2. move_to_end() — useful for LRU patterns
cache = OrderedDict()
cache['page1'] = 'content1'
cache['page2'] = 'content2'
cache['page3'] = 'content3'

cache.move_to_end('page1')  # move to most recently used
print(list(cache.keys()))   # ['page2', 'page3', 'page1']

cache.move_to_end('page2', last=False)  # move to front (LRU = least recently used)
print(list(cache.keys()))   # ['page2', 'page3', 'page1']
▶ Output
False
True
['page2', 'page3', 'page1']
['page2', 'page3', 'page1']

🎯 Key Takeaways

  • defaultdict(list) eliminates the if-key-not-in-dict pattern for grouping operations.
  • The factory function (list, int, set, or a lambda) is called with no arguments when a missing key is accessed.
  • Accessing a missing key in defaultdict creates it — be aware of this when iterating.
  • Regular dicts maintain insertion order since Python 3.7, so OrderedDict is mostly only needed for order-aware equality and move_to_end().
  • For counting, consider Counter from collections — it provides most_common() and is more expressive than defaultdict(int).

Interview Questions on This Topic

  • QWhat problem does defaultdict solve compared to a regular dict?
  • QIs OrderedDict still useful in Python 3.7 and later?
  • QHow would you group a list of items by a property without using defaultdict?

Frequently Asked Questions

Does accessing a missing key in defaultdict modify the dictionary?

Yes — that is the point. When you access dd['missing_key'], it calls the factory function, stores the result under 'missing_key', and returns it. This means if you iterate and access keys conditionally, you may end up with more keys than you intended.

Should I use OrderedDict or a regular dict in Python 3.7+?

Use a regular dict for most cases — insertion order is guaranteed. Use OrderedDict when you need order-sensitive equality (two dicts with the same items in different order should compare as unequal) or when you need move_to_end().

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← Previousheapq Module in Python
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged