AlgoMaster Logo

OrderedDict

Medium Priority16 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

collections.OrderedDict is a dictionary subclass that remembers the order keys were added. Regular dictionaries already preserve insertion order since Python 3.7, so OrderedDict looks redundant at first glance. It isn't. OrderedDict carries a few order-aware operations that plain dict doesn't have, and it compares two instances with order in mind. This lesson covers what OrderedDict still buys you in modern Python and the e-commerce scenarios where it's the right tool.

A Brief History

For most of Python's life, dict made no promise about key order. Iterating a dict could yield keys in any sequence, and that sequence could change between runs. If you needed predictable order, you reached for collections.OrderedDict, which was added in Python 3.1 for exactly this reason.

That changed in Python 3.6, when CPython's new dict implementation happened to preserve insertion order as an implementation detail. In Python 3.7, this became part of the language specification. Every Python 3.7+ implementation guarantees that iterating a dict yields keys in the order they were inserted.

So why does OrderedDict still exist? Two reasons. First, removing it would break a huge amount of older code. Second, and more importantly, OrderedDict has methods and behaviors that regular dicts don't have. The order-preservation overlap is real, but it's not the whole feature set.

This lesson focuses on what OrderedDict adds beyond the insertion-order guarantee that regular dicts already provide.

Same output you'd get from a regular dict today. The difference becomes visible only when you reach for one of the order-aware methods.

Creating an OrderedDict

The constructor takes the same kinds of inputs as dict: keyword arguments, an iterable of key-value pairs, or another mapping. The result is always a dictionary that records insertion order.

The repr shows the keys as a list of pairs, which is one easy way to tell at a glance whether you're looking at an OrderedDict or a plain dict in a debugger.

You can iterate, look up, and assign keys exactly the way you would on a regular dict. Every standard dict operation works. What's new lives in two methods and one comparison rule.

move_to_end: Shuffle a Key to Either End

move_to_end(key, last=True) moves an existing key to the right end of the ordering (the most recently added position). With last=False, it moves the key to the left end (the oldest position).

The key has to already exist. If it doesn't, you get a KeyError.

This is the operation that makes OrderedDict useful for "recency" patterns: every time a user views a product, you can move that product to the end of the list so the most recent views sit at the right side and the stale ones drift to the left.

After this sequence, the left side holds the products the shopper saw earliest and hasn't returned to. The right side holds the products they touched most recently. That's the building block for a recently-viewed widget, a browsing history, or an LRU cache.

popitem: LIFO or FIFO

Regular dicts have popitem, but OrderedDict.popitem(last=True) adds a last flag. last=True (the default) removes and returns the rightmost pair, which is LIFO behavior. last=False removes the leftmost pair, which is FIFO.

Compare this with regular dict.popitem(), which only supports LIFO (it removes the last-inserted pair) and has no last flag. If you need FIFO from a regular dict, you'd have to find the first key by iterating and then del it, which is more code and reads worse.

The last=False mode is what makes OrderedDict a clean fit for a FIFO queue when you also want key-based lookup. A plain collections.deque is faster if you only need ends, but a deque doesn't let you check membership or look up by key in constant time.

If the OrderedDict is empty, popitem raises KeyError, same as on a regular dict.

Order-Sensitive Equality

This is the one behavioral difference that catches people off guard. Two regular dicts are equal if they have the same keys mapped to the same values, regardless of insertion order. Two OrderedDict instances are equal only if the keys are also in the same order.

That second False is the headline. The two OrderedDict values map the same keys to the same values, but the orders differ, so they don't compare equal.

What about an OrderedDict compared against a regular dict? Python uses the regular-dict rules in that case: order is ignored, only keys and values matter.

The order-sensitive rule only applies when both sides are OrderedDict. When one side is a plain dict, the comparison falls back to the order-insensitive behavior. That's a deliberate compromise so OrderedDict and dict stay loosely interoperable.

This matters for cases where the sequence carries meaning, not just the keys. A few e-commerce examples:

  • Recipe step order. Two recipes with the same ingredients but a different cooking sequence are not the same recipe.
  • Navigation breadcrumbs. A trail of Home > Electronics > Mice is not the same as Home > Mice > Electronics (the second doesn't even make sense).
  • Browsing history. Two history lists with the same products in different orders represent different shopping journeys.

If you wrote this with a plain dict, the two journeys would compare equal and the bug would be invisible. With OrderedDict, the equality check captures what you actually meant.

Reverse Iteration: Same as Regular Dict

Reverse iteration over a dict works in both OrderedDict and regular dict since Python 3.8.

This works the same on a plain dict in Python 3.8+. Earlier versions of Python required an OrderedDict for reversed() to be defined on the mapping at all. In modern Python, there's parity on this specific operation. If you only need reverse iteration, you don't need OrderedDict.

A Sketch of an LRU Cache

The classic use case for OrderedDict is building an LRU (least recently used) cache from scratch. The idea is simple:

  • On every access, move the key to the end (most recently used).
  • When the cache is full and you need to insert, pop from the left (least recently used) to make room.

Both operations are O(1) on OrderedDict, which is exactly what you need.

cable got evicted because it was the oldest untouched entry when keyboard came in. mouse survived because cache.get("mouse") bumped it to the end before the eviction.

Here's what happens internally on each step:

The left side of each box is the least recently used, the right side is the most recently used. Every get moves the touched key to the right. Once capacity is exceeded, an insert pops the leftmost key and pushes the new one on the right.

In real code you'd reach for functools.lru_cache if you wanted to memoize function results, since it's a decorator and handles thread safety, statistics, and corner cases for you. The OrderedDict sketch above is the right tool when you need to manage a cache as data, not as a decorator: storing recently viewed products per user, capping a session's worth of search results, or limiting the size of a "what you looked at" widget that a frontend reads.

A FIFO Queue With Key Lookup

OrderedDict is a sensible choice when you want a queue (FIFO) that also lets you check or remove items by key. A plain list gives you indexed access but O(n) membership checks. A deque gives you O(1) ends but no key lookup. An OrderedDict gives you both, at a small memory cost.

popitem(last=False) pulls the next order in FIFO order. del pending["ORD-1003"] removes a specific order in O(1) thanks to the dict-style lookup. You couldn't do that cleanly with a deque.

A real production queue would use a database or a managed queue service for durability and concurrency. For an in-memory short-lived queue (a batch waiting to be flushed, a small per-session worklist), OrderedDict covers a lot of ground.

dict vs OrderedDict in Modern Python

A side-by-side view of what the two types share and where they differ:

Capabilitydict (3.7+)OrderedDict
Preserves insertion orderYesYes
popitem() removes last (LIFO)YesYes (default)
popitem(last=False) removes firstNo (TypeError)Yes (FIFO)
move_to_end(key, last=True/False)No (AttributeError)Yes
Reverse iteration with reversed()Yes (3.8+)Yes
Equality compares orderNo (order ignored)Yes (when both sides are OD)
Equality with a regular dictOrder ignoredOrder ignored (mixed compare)
Memory overheadLowerHigher (linked-list bookkeeping)
Speed of basic ops (get, set, in)Slightly fasterSlightly slower
__repr__ shows order in pair listNo (looks like {...})Yes (OrderedDict([...]))

The two types overlap on order preservation and reverse iteration. They diverge on the order-aware operations and on equality semantics.

When should you reach for OrderedDict in modern Python? The honest answer is "rarely, but specifically." If your code uses move_to_end, popitem(last=False), or relies on order-sensitive equality, OrderedDict is the right tool. If it doesn't, a plain dict is simpler and faster.

These two errors are the litmus test. If your code needs to do either of these, switch from dict to OrderedDict. Otherwise, you're paying for features you don't use.

Memory and Performance Note

The exact overhead has shrunk over Python versions (the linked-list bookkeeping is more compact than it used to be), but OrderedDict is still measurably larger and slightly slower than dict for the same data. You can see the size difference yourself:

The OrderedDict is roughly 50% larger here because of the extra per-entry pointers it maintains. For five entries the gap is meaningless, but the ratio is similar at scale. If you're storing recently-viewed items for a single user (a few dozen entries), this never matters. If you're storing one of these per row in a million-row table, it adds up.

In practice the decision rarely comes down to memory. It comes down to whether you need the order-aware methods. If you do, the overhead is the price of admission. If you don't, you don't pay it.

When to Reach for OrderedDict

A decision flow for choosing between dict, OrderedDict, and deque:

The three branches map to three patterns:

  • `OrderedDict` when you're doing LRU-style recency management, FIFO with key lookup, or comparing sequences by order.
  • `dict` when you want fast key-value lookup and you don't care about order operations beyond insertion-order iteration.
  • `deque` when you only need ends (push/pop from front or back) and don't need to look up by key.

Don't overuse OrderedDict out of habit. In a modern codebase, the default container for key-value data is dict, and OrderedDict is a specific tool you pick when the problem actually calls for one of its order-aware features.

Quiz

OrderedDict Quiz

10 quizzes