How to Implement an O(1) LRU Cache Using a Doubly Linked List and Hash Map
Achieve strict O(1) get() and put() operations by combining a hash table with sentinel head/tail doubly linked nodes.
An O(1) Least Recently Used (LRU) cache pairs a hash map (mapping keys to node pointers for O(1) lookup) with a doubly linked list bounded by dummy head and tail sentinels (enabling O(1) node splice and insertion at the MRU front). On every get(key) or put(key, val), the accessed node is unlinked and moved immediately after head; when capacity is exceeded, head's opposite sentinel (tail.prev) is evicted in O(1).
Interactive Runnable CodeSandbox
LRUCache (Doubly Linked List + Dict)
Pointer Invariant & Amortized Operations
Every cache access performs at most 6 pointer reassignments in the doubly linked list plus 1 hash table lookup.
Step-by-Step Implementation Breakdown
Eliminate Null-Pointer Branches with Dummy Sentinels
Initialize two permanent sentinel nodes (head and tail) connected to each other. Every real cache node lives strictly between head and tail, so _remove() and _insert_mru() never branch on None.
Store Key Inside Node for O(1) Reverse Eviction
When evicting tail.prev, we must also delete its entry from self.cache. Storing node.key on the doubly linked node avoids an O(N) scan of the dictionary.
Use __slots__ in Python for 40% Lower Node Memory Overhead
Declaring __slots__ = ('key', 'val', 'prev', 'next') prevents CPython from allocating a per-instance __dict__ on every linked-list node.
Production Edge Cases & Interview Traps
- Updating an existing key via put(k, new_val) must promote k to MRU and must NOT trigger an eviction of another key.
- In multi-threaded environments, wrap _remove/_insert_mru with a shard lock (or use Segmented LRU to reduce lock contention).
Frequently Asked Questions
Why can't we use a singly linked list with a hash map for an O(1) LRU cache?
Deleting an arbitrary middle node from a singly linked list requires knowing its predecessor node, which takes O(N) traversal unless you maintain back-pointers (making it a doubly linked list).
How does Python's built-in collections.OrderedDict implement LRU?
OrderedDict is implemented in C using the exact same architecture: a dict hash table paired with a circular doubly linked list, exposing move_to_end(key, last=False) and popitem(last=True) in O(1).

