← Back to Python

DefaultDict, OrderedDict

PythonMidpython

The Question

What are defaultdict and OrderedDict and when would you use them?

What a Strong Answer Covers

  • factory on missing key
  • "move_to_end()
  • popitem()
  • "dict ordered since 3.7

Senior-Level Answer

`defaultdict` and `OrderedDict` are both subclasses of `dict` in `collections`, each adding a specialized behavior on top of the standard dictionary.

**`defaultdict`** takes a *default_factory* callable as its first argument. When you access a key that doesn't exist, instead of raising `KeyError`, it calls `default_factory()` to create a default value, inserts it under that key, and returns it. This eliminates the common `dict.setdefault()` or `if key not in d` boilerplate.

Common default factories: - `defaultdict(list)` — grouping: `d[key].append(item)` works without initializing the list - `defaultdict(int)` — counting: `d[key] += 1` works without checking if key exists - `defaultdict(set)` — grouping unique values: `d[key].add(item)` - `defaultdict(lambda: defaultdict(int))` — nested counting tables

Important nuance: `default_factory` is only invoked by `__missing__`, which is called by `__getitem__`. Using `d.get(key)` does *not* trigger the factory — it returns `None` (or the specified default). This catches people who expect `.get()` to auto-create.

**`OrderedDict`** preserves insertion order and exposes `move_to_end(key, last=True)` to move a key to the beginning or end. In Python 3.7+, the built-in `dict` also preserves insertion order as a language guarantee (not just an implementation detail). This makes `OrderedDict` less necessary for most use cases.

However, `OrderedDict` still has two advantages: it provides `move_to_end()` for O(1) reordering (implemented as a doubly-linked list), and its `__eq__` considers order—two `OrderedDict`s with the same keys but different insertion order are *not* equal, whereas regular `dict`s consider only key-value pairs.

Practical use: `OrderedDict` as an LRU cache backbone — `move_to_end` on access, `popitem(last=False)` to evict the oldest. This is the pattern Python's `functools.lru_cache` internally mirrors.

Both classes inherit all standard `dict` methods, so they're drop-in replacements where appropriate.

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Explains defaultdict factory mechanism and gives two concrete use cases (counting, grouping), correctly states that Python 3.7+ dict preserves order, identifies move_to_end as OrderedDict's key differentiator.

3/3 — Strong Answer

Explains that .get() doesn't trigger defaultdict factory, mentions __missing__ as the mechanism, explains OrderedDict.__eq__ considers order while dict.__eq__ doesn't, and gives the LRU cache pattern as a concrete OrderedDict use case.

Common Mistakes

  • Not knowing that dict.get() bypasses defaultdict's factory — common source of bugs
  • Saying OrderedDict is necessary for insertion-order preservation in Python 3.7+ — it's not; regular dict preserves order now
  • Confusing OrderedDict with SortedDict — OrderedDict preserves insertion order, not sorted order
  • Forgetting that OrderedDict.__eq__ is order-sensitive while dict.__eq__ is not

Follow-Up Questions

  • Why doesn't d.get(key) trigger the defaultdict factory? — defaultdict overrides __missing__, which is only called by __getitem__ (d[key]). dict.get() has its own implementation that checks for the key and returns the default parameter without calling __missing__.
  • When would you still use OrderedDict in Python 3.7+? — When you need move_to_end() for O(1) LRU-style reordering, or when equality comparison must be order-sensitive (two dicts with same pairs in different order must not be equal), or for code that must be explicit about ordering contracts.
  • How would you implement a simple LRU cache using OrderedDict? — On cache hit: move_to_end(key) to mark as recently used. On cache miss: insert new key, then if over capacity, popitem(last=False) to evict the least recently used (oldest entry).
  • What is the default_factory for defaultdict(list) and what happens if you don't pass any factory? — defaultdict(list) uses the list constructor as factory, so missing keys get []. If you pass no factory (defaultdict()), factory is None — KeyError is raised on missing keys, behaving like a regular dict.

Related Questions

  • GIL — What It Is and What It Protects
  • ThreadPoolExecutor & ProcessPoolExecutor
  • Decorators — Under the Hood
  • Generators & yield
  • Context Managers

Can You Explain This Cold?

Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.

Get Graded — Free Assessment
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout