Python OrderedDict vs dict: When You Still Need It

Python dict keeps insertion order since 3.7. Learn when OrderedDict still helps with move_to_end(), popitem(last=False), equality and an LRU cache.

Python

An OrderedDict is a dictionary subclass from the collections module that remembers insertion order and adds methods to reorder its keys; since Python 3.7, a regular dict also keeps insertion order, so OrderedDict is only needed for its extra features today. Those features are move_to_end(), popitem(last=False) and order-sensitive equality, which make it the classic base for an LRU cache.

from collections import OrderedDict

user = OrderedDict(name="lokesh", id=100, email="admin@gmail.com")
user.move_to_end("name")            # keys: id, email, name
first = user.popitem(last=False)    # ('id', 100)

We compare OrderedDict with dict, create and update one, reorder keys, check equality, and build a small LRU cache. The examples run on Python 3.14.

1. OrderedDict vs dict

Older articles say that a regular dictionary returns its keys in arbitrary order. That was true before Python 3.6; since Python 3.7, insertion order is part of the language guarantee. The differences that remain are in the extra methods and in equality.

FeaturedictOrderedDict
Keeps insertion orderYes (3.7+)Yes
move_to_end(key, last=True)NoYes
popitem(last=False), remove the first itemNo (popitem() removes the last)Yes
Equality checks orderNoYes, between two OrderedDicts
MemorySmallerLarger

2. Creating and Updating an OrderedDict

An OrderedDict is created like a dict, from keyword arguments, a list of pairs or fromkeys(). Updating an existing key keeps its position, and a new key goes to the end.

user = OrderedDict()
user["name"] = "lokesh"
user["id"] = 100
user["email"] = "admin@gmail.com"
user["email"] = "admin@howtodoinjava.com"   # position unchanged
user["location"] = "India"                  # added at the end
keys = list(user)                           # ['name', 'id', 'email', 'location']
defaults = OrderedDict.fromkeys(["id", "name"], None)   # OrderedDict({'id': None, 'name': None})

Deleting a key with del and adding it again places it at the end, because it counts as a new insertion.

3. Reordering with move_to_end() and popitem()

The move_to_end() method moves an existing key to the end, or to the front with last=False, in constant time. The popitem() method removes the last item by default and the first item with last=False.

od = OrderedDict.fromkeys("abcde")
od.move_to_end("b")                  # a c d e b
od.move_to_end("e", last=False)      # e a c d b
last = od.popitem()                  # ('b', None)
first = od.popitem(last=False)       # ('e', None)
missing = od.move_to_end("z")        # KeyError: 'z'

A regular dict can only remove its last item with popitem(); moving a key to the front needs rebuilding the whole dictionary.

4. Equality Checks Order

Two OrderedDict objects are equal only when they have the same items in the same order. Comparing an OrderedDict with a regular dict ignores the order, like comparing two dicts.

a = OrderedDict([("x", 1), ("y", 2)])
b = OrderedDict([("y", 2), ("x", 1)])
same_od = a == b                     # False, order differs
same_dict = dict(a) == dict(b)       # True
mixed = a == {"y": 2, "x": 1}        # True, a plain dict ignores order

5. Building an LRU Cache

A least recently used (LRU) cache keeps a fixed number of entries and evicts the one that has not been used for the longest time. With move_to_end() on each access and popitem(last=False) on overflow, the oldest entry is always at the front.

class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.data = OrderedDict()

    def get(self, key):
        if key not in self.data:
            return None
        self.data.move_to_end(key)
        return self.data[key]

    def put(self, key, value):
        self.data[key] = value
        self.data.move_to_end(key)
        if len(self.data) > self.capacity:
            self.data.popitem(last=False)

cache = LRUCache(2)
cache.put("a", 1)
cache.put("b", 2)
cache.get("a")                       # touches a, so b is now the oldest
cache.put("c", 3)                    # evicts b
keys = list(cache.data)              # ['a', 'c']

For caching function results, functools.lru_cache does the same with a decorator and needs no class.

6. OrderedDict FAQs

Code written for Python 3.6 and older often still imports OrderedDict, which raises the question of when to keep it.

6.1. Should new code use OrderedDict or dict?

A plain dict is the default, because it keeps insertion order and uses less memory. OrderedDict is the right choice when the code reorders keys or needs order-sensitive equality.

6.2. Can we iterate an OrderedDict in reverse?

Yes, reversed(od) yields the keys from last to first. A regular dict supports reversed() as well since Python 3.8.

6.3. Does json keep the order of an OrderedDict?

Yes. json.dumps() writes keys in iteration order for both dict and OrderedDict, and json.loads(text, object_pairs_hook=OrderedDict) reads them back into an OrderedDict.

7. Conclusion

Regular dictionaries keep insertion order since Python 3.7, so new code starts with dict and reaches for OrderedDict when keys must be reordered. A small LRU cache is the typical case, and functools.lru_cache covers caching function results without any class.

8. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.