Implement an LRU cache

Coding Medium GoogleMetaAmazon
Reference
Start your answer

Remix this answer and make it yours, or start blank. Either way you work beside the reference, one chapter at a time, and it stays private until you publish it.

Problem statement

Implement a least-recently-used cache with get(key) and put(key, value), both in O(1) time, with a fixed capacity.

Approach

Keep a hash map from key to node, and a doubly linked list ordered by recency (most recent at the head). get moves the node to the head; put inserts at the head and evicts from the tail when over capacity. Both are O(1).

Sentinel head and tail nodes remove the empty-list edge cases.

Code
class Node:
    __slots__ = ("key", "value", "prev", "next")

    def __init__(self, key=0, value=0):
        self.key, self.value = key, value
        self.prev = self.next = None


class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.map = {}
        self.head, self.tail = Node(), Node()
        self.head.next, self.tail.prev = self.tail, self.head

    def _remove(self, node):
        node.prev.next, node.next.prev = node.next, node.prev

    def _push_front(self, node):
        node.prev, node.next = self.head, self.head.next
        self.head.next.prev = node
        self.head.next = node

    def get(self, key: int) -> int:
        node = self.map.get(key)
        if not node:
            return -1
        self._remove(node)
        self._push_front(node)
        return node.value

    def put(self, key: int, value: int) -> None:
        if key in self.map:
            self._remove(self.map[key])
        node = Node(key, value)
        self.map[key] = node
        self._push_front(node)
        if len(self.map) > self.capacity:
            lru = self.tail.prev
            self._remove(lru)
            del self.map[lru.key]
Complexity
  • get and put: O(1) time.
  • Space: O(capacity).
Test cases
Operations Expected
cap 2: put(1,1), put(2,2), get(1), put(3,3), get(2) 1, then -1 (2 was evicted)
cap 1: put(1,1), put(1,2), get(1) 2 (update, no eviction)
cap 2: get(5) on empty cache -1

Follow-ups interviewers ask: make it thread-safe (a lock around both operations, or striped locks), add TTL expiry, or use OrderedDict.move_to_end in Python.