LRU Cache

Difficulty: Medium 🏷️ Pattern: HashMap + Doubly Linked List 🏢 Asked at: PhonePe, Amazon, Google, Meta


Problem

Design a cache with a fixed capacity supporting:

Both operations must be O(1).


Approach

Why two data structures

The map stores key → node. The list orders nodes by recency: most recently used at the head, least recently used at the tail.

Operations

Sentinel head and tail dummy nodes remove all null-edge-case branching.


Complexity

  Time Space
get / put O(1) O(capacity)

Solution

class LRUCache {
    class Node { int key, val; Node prev, next;
        Node(int k, int v) { key = k; val = v; } }

    private final Map<Integer, Node> map = new HashMap<>();
    private final Node head = new Node(0, 0), tail = new Node(0, 0);
    private final int capacity;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        head.next = tail; tail.prev = head;
    }

    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        Node n = map.get(key);
        remove(n); addFront(n);
        return n.val;
    }

    public void put(int key, int value) {
        if (map.containsKey(key)) remove(map.get(key));
        Node n = new Node(key, value);
        map.put(key, n); addFront(n);
        if (map.size() > capacity) {
            Node lru = tail.prev;
            remove(lru); map.remove(lru.key);
        }
    }

    private void remove(Node n) { n.prev.next = n.next; n.next.prev = n.prev; }
    private void addFront(Node n) {
        n.next = head.next; n.prev = head;
        head.next.prev = n; head.next = n;
    }
}
class Node:
    def __init__(self, k=0, v=0):
        self.key, self.val = k, v
        self.prev = self.next = None

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

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

    def _add_front(self, n):
        n.next, n.prev = self.head.next, self.head
        self.head.next.prev = n
        self.head.next = n

    def get(self, key):
        if key not in self.map:
            return -1
        n = self.map[key]
        self._remove(n); self._add_front(n)
        return n.val

    def put(self, key, value):
        if key in self.map:
            self._remove(self.map[key])
        n = Node(key, value)
        self.map[key] = n
        self._add_front(n)
        if len(self.map) > self.cap:
            lru = self.tail.prev
            self._remove(lru)
            del self.map[lru.key]
class LRUCache {
    struct Node { int key, val; Node *prev, *next;
        Node(int k, int v): key(k), val(v), prev(nullptr), next(nullptr) {} };
    unordered_map<int, Node*> map;
    Node *head, *tail;
    int cap;

    void remove(Node* n) { n->prev->next = n->next; n->next->prev = n->prev; }
    void addFront(Node* n) {
        n->next = head->next; n->prev = head;
        head->next->prev = n; head->next = n;
    }
public:
    LRUCache(int capacity): cap(capacity) {
        head = new Node(0, 0); tail = new Node(0, 0);
        head->next = tail; tail->prev = head;
    }
    int get(int key) {
        if (!map.count(key)) return -1;
        Node* n = map[key];
        remove(n); addFront(n);
        return n->val;
    }
    void put(int key, int value) {
        if (map.count(key)) remove(map[key]);
        Node* n = new Node(key, value);
        map[key] = n; addFront(n);
        if ((int)map.size() > cap) {
            Node* lru = tail->prev;
            remove(lru); map.erase(lru->key); delete lru;
        }
    }
};

Try It Yourself

Write your solution and run it live in C++, Java, Python, Go, Rust, and more — right here in the browser.

▶ Try it live
stdin
Output
Run your code to see output here.

Key Insight

O(1) LRU needs two structures working together: a hash map for instant lookup and a doubly linked list for instant reordering/eviction. Neither alone can do both. Sentinel head/tail nodes eliminate null checks so remove and addFront are branch-free.


PhonePe angle: thread-safety follow-up

PhonePe often asks “make it thread-safe” after you code this. Options:


Follow-ups



Drop a comment below if you want the thread-safe implementation 👇

Discussion

Newest first
You