ordered dict
defaultdict
Python programming
Python collections
data structures

How to implement an ordered, default dict?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

An ordered default dict combines two behaviors: automatic values for missing keys and predictable key order. In modern Python, plain dict already preserves insertion order, but if you specifically want OrderedDict behavior plus a default_factory, a small subclass is still the clean way to express it.

Understand the Modern Python Context

Before writing custom code, remember that Python 3.7+ guarantees insertion order for normal dictionaries. That means a plain defaultdict already preserves insertion order in everyday use:

python
1from collections import defaultdict
2
3d = defaultdict(list)
4d["b"].append(2)
5d["a"].append(1)
6
7print(d)  # order of insertion is preserved

So if all you need is:

  • missing-key defaults
  • insertion order

then defaultdict may already be enough.

You still need a custom type when you want explicit OrderedDict methods such as move_to_end or when you want the intent to be obvious in the API.

That distinction matters in older codebases too. Some projects still rely on OrderedDict-specific behavior even though plain dictionaries now preserve insertion order.

Implement It by Subclassing OrderedDict

The usual implementation is to subclass OrderedDict and add a default_factory plus __missing__.

python
1from collections import OrderedDict
2
3
4class OrderedDefaultDict(OrderedDict):
5    def __init__(self, default_factory=None, *args, **kwargs):
6        if default_factory is not None and not callable(default_factory):
7            raise TypeError("default_factory must be callable or None")
8        self.default_factory = default_factory
9        super().__init__(*args, **kwargs)
10
11    def __missing__(self, key):
12        if self.default_factory is None:
13            raise KeyError(key)
14        value = self.default_factory()
15        self[key] = value
16        return value
17
18    def __repr__(self):
19        return (
20            f"{self.__class__.__name__}"
21            f"({self.default_factory!r}, {list(self.items())!r})"
22        )

That gives you default creation on first access while preserving the normal ordered mapping behavior.

Usage looks familiar:

python
1odd = OrderedDefaultDict(list)
2odd["letters"].append("a")
3odd["numbers"].append(1)
4odd["letters"].append("b")
5
6print(odd)
7print(list(odd.keys()))

The first time a missing key is accessed, __missing__ creates the value, stores it, and returns it.

That means the access both reads and mutates the mapping, just like defaultdict. It is a feature, but callers should be aware of it.

Why __missing__ Is the Right Hook

dict-like mappings call __missing__ when obj[key] fails for a key that does not exist. That makes it the correct place to create default values lazily.

The lazy part matters because you do not want to create values for every possible key up front. You want them only when a missing key is actually used.

It also avoids a common bug where all keys accidentally share the same mutable default object. Using a default_factory such as list or set ensures each missing key gets a fresh container.

For example, OrderedDefaultDict(list) gives every missing key its own empty list, while a single shared list object would corrupt the structure immediately.

Common Pitfalls

  • Writing the class even though plain defaultdict already satisfies the real requirement in modern Python.
  • Passing a mutable object instead of a factory function, which causes shared state across keys.
  • Forgetting to assign the created value back into the dictionary inside __missing__.
  • Assuming __missing__ is triggered by methods like .get(). It is triggered by obj[key] lookup, not every access style.
  • Forgetting that missing-key access mutates the mapping, which can surprise callers during debugging or iteration.

Summary

  • In modern Python, plain defaultdict already preserves insertion order.
  • A custom OrderedDefaultDict is still useful when you specifically want OrderedDict semantics plus defaults.
  • The usual implementation subclasses OrderedDict and defines __missing__.
  • Use a callable default_factory so each missing key gets its own value.
  • Check whether you really need the custom type before adding it to the codebase.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.