---
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