OrderedDict Quirks Worth Knowing
Regular dicts have preserved insertion order since Python 3.7, so most modern code never reaches for `OrderedDict`. But OrderedDict still has a niche: it ships with `move_to_end` and `popitem(last=False)` methods that plain dicts do not, and its equality semantics differ from dict equality. This snippet covers the move-to-end LRU primitive, the order-sensitive equality, and when you should still pick OrderedDict in 2025.
577 views
5
from collections import OrderedDict
cache = OrderedDict()
cache['a'] = 1
cache['b'] = 2
cache['c'] = 3
# Refresh 'a' as most-recently-used
cache.move_to_end('a')
print(list(cache)) # ['b', 'c', 'a']
# Promote 'b' to least-recently-used
cache.move_to_end('b', last=False)
print(list(cache)) # ['b', 'c', 'a']
# Pop the oldest (FIFO eviction in an LRU cache)
oldest = cache.popitem(last=False)
print(oldest) # ('b', 2)
print(list(cache)) # ['c', 'a']move_to_end(key) is the operation a hand-rolled LRU cache needs every time a key is read or written: bump it to the most-recently-used end. The companion popitem(last=False) evicts the least-recently-used entry in O(1). Plain dicts do not expose either method, which is the main reason OrderedDict still earns its keep in 2025. The same pair powers Python's functools.lru_cache under the hood.
from collections import OrderedDict
a = OrderedDict([('x', 1), ('y', 2)])
b = OrderedDict([('y', 2), ('x', 1)])
print(a == b) # False (OrderedDict cares about order)
print(dict(a) == dict(b)) # True (dict ignores order)
print(a == {'x': 1, 'y': 2}) # True (mixed compare uses dict semantics)
# Useful for testing serialisation that must preserve key order
import json
print(json.dumps(a)) # {"x": 1, "y": 2}Two OrderedDicts are equal only if they have the same keys, the same values, AND the same insertion order. Two regular dicts are equal even with different orders. Mixed comparisons (OrderedDict vs dict) drop down to the dict semantics. This stricter equality is exactly what you want when testing JSON serialisation, comparing parsed YAML, or asserting that a stable order is preserved through a transformation. It is also a sharp edge: never assume OrderedDict == OrderedDict matches dict == dict.
from collections import OrderedDict
# Pick OrderedDict when you need: move_to_end, popitem(last=False), or order equality.
# Pick plain dict for everything else.
# Example: build a tiny LRU cache in a few lines
class LRU:
def __init__(self, cap):
self.cap = cap
self.cache = OrderedDict()
def get(self, k):
if k not in self.cache:
return None
self.cache.move_to_end(k)
return self.cache[k]
def put(self, k, v):
if k in self.cache:
self.cache.move_to_end(k)
self.cache[k] = v
if len(self.cache) > self.cap:
self.cache.popitem(last=False)
c = LRU(2)
c.put('a', 1); c.put('b', 2); c.get('a'); c.put('c', 3)
print(c.get('a')) # 1
print(c.get('b')) # None (evicted)
print(c.get('c')) # 3The 12-line LRU above is the smallest production-ready cache you can write in Python, and it is impossible without move_to_end and popitem(last=False). For most other cases (configs, request payloads, parsed query strings) plain dict is faster, more memory-efficient, and IDE-friendlier. Reach for OrderedDict when the order itself is part of the contract or when the LRU primitives are required. Otherwise default to dict.
