---
title: '146. LRU Cache'
description: Design a data structure that follows the constraints of a Least Recently Used (LRU) cache
sidebar:
  label: 'LRU Cache'
  badge: 'Medium'
---

Hash Table

### Example 1:
- Input: ``
- Output: ``
- Explanation: Input ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] `[[2]`, [1, 1], [2, 2], `[1]`, [3, 3], `[2]`, [4, 4], `[1]`, `[3]`, [4]] Output [null, null, null, `1`, null, `-1`, null, `-1`, `3`, 4] Explanation LRUCache lRUCache = new LRUCache(2); lRUCache.put(1, `1`); // cache is {1=1} lRUCache.put(2, `2`); // cache is {1=1, 2=2} lRUCache.get(1);    // return `1` lRUCache.put(3, `3`); // LRU `key` was `2`, evicts `key 2`, cache is {1=1, 3=3} lRUCache.get(2);    // returns `-1` (not found) lRUCache.put(4, `4`); // LRU `key` was `1`, evicts `key 1`, cache is {4=4, 3=3} lRUCache.get(1);    // return `-1` (not found) lRUCache.get(3);    // return `3` lRUCache.get(4);    // return `4`

### Constraints:

- `1 <= capacity <= 3000`
- `0 <= key <= 10^4`
- `0 <= value <= 10^5`
- At most 2 * 10^5 calls will be made to get and put.

## Approach

```mermaid
flowchart TD
  subgraph state["state — dict for O(1) lookup, doubly linked list for O(1) reorder"]
    H["head — least recent"] --- N["... nodes ..."] --- T["tail — most recent"]
  end
  subgraph get["get(key)"]
    G1{"key in cache?"} -- no --> G2(["return -1"])
    G1 -- yes --> G3["_remove_node then _add_node — move it beside the tail"]
    G3 --> G4(["return node.value"])
  end
  subgraph put["put(key, value)"]
    P1{"key in cache?"} -- yes --> P2["update value, _remove_node then _add_node"]
    P1 -- no --> P3["new Node, cache[key] = node, _add_node"]
    P3 --> P4{"len(cache) > capacity?"}
    P4 -- yes --> P5["evict head.next — the least recent — and del cache[lru.key]"]
    P4 -- no --> P6(["done"])
    P2 --> P6
    P5 --> P6
  end
  state --> get
  state --> put
```

## Solution

```py
class Node:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None


class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.cache = {}
        self.head = Node()
        self.tail = Node()
        self.head.next = self.tail  # pyright: ignore[reportAttributeAccessIssue]
        self.tail.prev = self.head  # pyright: ignore[reportAttributeAccessIssue]

    def _remove_node(self, node):
        prev_node = node.prev
        next_node = node.next
        prev_node.next = next_node
        next_node.prev = prev_node

    def _add_node(self, node):
        # Insert just before the tail (most recent at end)
        node.next = self.tail
        node.prev = self.tail.prev
        self.tail.prev.next = node  # pyright: ignore[reportAttributeAccessIssue]
        self.tail.prev = node

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1

        node = self.cache[key]
        self._remove_node(node)
        self._add_node(node)
        return node.value

    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            node = self.cache[key]
            node.value = value
            self._remove_node(node)
            self._add_node(node)
        else:
            new_node = Node(key, value)
            self.cache[key] = new_node
            self._add_node(new_node)

            if len(self.cache) > self.capacity:
                lru = self.head.next  # Oldest node is next to the head
                self._remove_node(lru)
                del self.cache[lru.key]  # pyright: ignore[reportAttributeAccessIssue]


# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
```

## Explanation

[LRU Cache - Twitch Interview Question - Leetcode 146](https://www.youtube.com/watch?v=7ABFKPK2hD4)
