What does collections.OrderedDict still offer now that dict preserves insertion order?
answer
- ordering alone stopped being a reason
- what dict never got, not what it got
- reposition a key without re-inserting it
- pop from the oldest end
- equality that counts order — between two of them
basics
~20 sThree things dict lacks: move_to_end(key, last=) to reposition a key in O(1), popitem(last=False) to pop the oldest entry, and order-sensitive equality between two OrderedDicts. Since Python 3.7 plain dict keeps insertion order, so ordering alone is no reason.
solid answer
~40 sSince Python 3.7 insertion order is a language guarantee for `dict`, so ordering alone is no longer a reason to reach for `collections.OrderedDict`. What survives is behaviour `dict` never got: `move_to_end(key, last=True/False)` repositions an existing key in constant time at either end; `popitem(last=False)` removes the **oldest** entry, while `dict.popitem()` only ever removes the newest; and `OrderedDict.__eq__` is **order-sensitive when both sides are OrderedDicts**, so two mappings with the same pairs in a different order compare unequal. Comparison against a plain `dict` stays order-insensitive. The price is memory — the doubly linked list behind the ordering makes it noticeably larger than the equivalent `dict`. Use `dict` by default; use `OrderedDict` when reordering or order-as-data is the point.
code
pycon · 7 lines>>> from collections import OrderedDict
>>> OrderedDict(a=1, b=2) == OrderedDict(b=2, a=1)
False
>>> OrderedDict(a=1, b=2) == {"b": 2, "a": 1}
True
>>> {"a": 1, "b": 2} == {"b": 2, "a": 1}
Truego deeper
Know that plain dict has kept insertion order since Python 3.7, so you no longer import OrderedDict just to iterate in order. Remember that one thing it still does is move an existing key to either end.
Explain all three survivors precisely: move_to_end(key, last=), popitem(last=False) versus dict.popitem()'s newest-only removal, and order-sensitive equality that applies only between two OrderedDicts.
Show the judgment: default to dict, and justify OrderedDict by the operation you need — reordering, oldest-end eviction, or order as part of equality — while accounting for its roughly doubled per-object footprint.
Treat the type as documentation. Decide when order is part of a contract worth enforcing in == versus when relying on it silently creates coupling that a future refactor will break without a failing test.
## What changed, and when In CPython 3.6 the compact dict implementation happened to preserve insertion order, but that was an implementation detail nobody was allowed to rely on. In **Python 3.7** it became a language guarantee: every conforming implementation must iterate a `dict` in insertion order. That single sentence removed the most common reason to import `collections.OrderedDict`, and it is why the question gets asked — the interviewer wants to know whether you stopped at the headline or read the rest. ## The three things that survive **1. `move_to_end(key, last=True)`.** Given a key already present, this moves it to the newest end, or to the oldest end with `last=False`. It is O(1): the entry is unlinked from the doubly linked list that tracks order and relinked at the requested end. A plain `dict` has no such method at all. The nearest equivalent is `d[k] = d.pop(k)`, which reaches only the newest end, is a delete plus an insert rather than a relink, and quietly breaks if the value must not be re-created. **2. `popitem(last=False)`.** `dict.popitem()` always pops the most recently inserted pair — LIFO, and it takes no arguments. `OrderedDict.popitem()` defaults to the same behaviour but accepts `last=False` to pop the **oldest** pair in O(1). That is the primitive for FIFO drain and for any bounded structure that evicts the least recently touched entry: touch with `move_to_end`, evict with `popitem(last=False)`. **3. Order-sensitive equality.** Two `OrderedDict` objects compare equal only if their items are equal **and** in the same order. Two plain dicts with the same pairs are equal regardless of order, and — importantly — comparing an `OrderedDict` to a plain `dict` also falls back to the order-insensitive rule. So the strictness applies only when both operands are `OrderedDict`. That makes the type a real tool when order is part of the value: canonical header sequences, an ordered column list, a signed request whose parameter order is meaningful. A fourth, smaller point: subclassing. `OrderedDict` was designed to be subclassed and its internals cooperate with the ordering, which is why the standard library's own least-recently-used machinery historically leaned on it. Nothing stops you subclassing `dict`, but you get no reordering primitive. ## What it costs The linked list is not free. On CPython 3.14 a two-key `OrderedDict` reports roughly double the `sys.getsizeof` of the equivalent `dict`, and every entry carries link overhead. Construction and item assignment are also a little slower. For a handful of mappings none of this matters; for millions of small mappings it is the difference between fitting in memory and not. There is a second, subtler cost: **intent drift**. Code that says `OrderedDict` in 2016 usually meant "I need ordering"; code that says it today should mean "I need reordering or order-sensitive comparison". A reader who cannot tell which you meant has to check every use. ## What `dict` gained in the meantime Beyond the ordering guarantee, `dict` became reversible in **Python 3.8**, so `reversed(d)`, `reversed(d.items())` and friends work without conversion — another former reason to import `OrderedDict` that no longer applies. `dict.popitem()` popping the newest entry has been the documented behaviour since 3.7 as well. Between them, plain `dict` now covers ordered iteration, reverse iteration and stack-shaped removal. ## Choosing between them Default to `dict`. It is smaller, faster, the literal syntax is native, and every reader knows it. Reach for `OrderedDict` when one of these is true: * you reorder existing keys (`move_to_end`) rather than only appending; * you remove from the **oldest** end (`popitem(last=False)`); * order is semantically part of equality and you want `==` to enforce it; * you want the type itself to document that order is load-bearing to a future reader. And be precise in an interview about the equality rule: "order-sensitive between two OrderedDicts, order-insensitive against a plain dict" is the sentence that separates a candidate who read the documentation from one who repeated a blog headline. ## Two details that separate answers `move_to_end` requires the key to already be present — it raises `KeyError` otherwise, so it is a reordering primitive, not an upsert. And the ordering it maintains is the *insertion/reorder* order, not a sort: neither type sorts anything, and reaching for `OrderedDict` when the requirement is "sorted by key" is a category error that `sorted(d.items())` answers instead. The second detail is what an interviewer is really probing. "Is OrderedDict dead?" is a proxy for whether you track how the language changed and whether you read past the release-note headline. A candidate who says "3.7 made dict ordered, so OrderedDict is obsolete" has half the fact and the wrong conclusion; a candidate who names the three surviving behaviours, the release that changed the rest, and the memory cost has shown the habit the question exists to find.
- How would you move a key to the end of a plain dict, and how does that differ from move_to_end?`d[k] = d.pop(k)` — delete the entry and re-insert it, which lands it at the newest end. It only reaches that end, it re-creates the entry rather than relinking it, and it briefly removes the key, so a concurrent reader or a `KeyError`-sensitive path can see the gap. `move_to_end` relinks in place and also reaches the oldest end with `last=False`.
- Does OrderedDict == dict ever compare order?No. The order-sensitive rule applies only when both operands are `OrderedDict`. Compared against a plain `dict`, the ordinary mapping equality applies and order is ignored in both directions. That asymmetry surprises people who assume the stricter type always wins the comparison.
- What does dict.popitem() remove, and can you ask it for the oldest entry?It removes and returns the most recently inserted pair — LIFO — and it takes no arguments, so there is no way to ask for the oldest. That is exactly the gap `OrderedDict.popitem(last=False)` fills; with a plain `dict` you would have to take `next(iter(d))` and delete that key yourself.
- Is there a memory cost to preferring OrderedDict everywhere?Yes. The ordering is maintained by a doubly linked list, so each instance carries per-entry link overhead — on CPython 3.14 a small `OrderedDict` reports roughly twice the `sys.getsizeof` of the equivalent `dict`. Irrelevant for a few objects; material when you hold millions of small mappings.
saying these in an interview costs you the question
- Says OrderedDict is obsolete since Python 3.7
- Claims dict has a move_to_end method
- Thinks dict.popitem() can pop the oldest entry
- Believes OrderedDict == dict compares order
- Says dict ordering was guaranteed in 3.6
- Assumes OrderedDict costs the same memory as dict