Problem statement
Design a Least Recently Used (LRU) cache with a fixed positive capacity.
get(key) returns the value stored for key, or -1 if the key is absent. put(key, value) inserts the key or updates its value. When an insert pushes the cache over capacity, evict the least recently used key.
A successful get and any put both count as using that key. Both operations must run in O(1) average time.
Examples
- Input
capacity = 2 put(1, 1), put(2, 2), get(1), put(3, 3), get(2), put(4, 4), get(1), get(3), get(4)
- Output
1, -1, -1, 3, 4
Why: The outputs are the five get results in order. get(1) makes key 1 the most recent, so put(3, 3) evicts key 2; then put(4, 4) evicts key 1.
- Input
capacity = 2 put(1, 1), put(2, 2), put(1, 10), put(3, 3), get(1), get(2)
- Output
10, -1
Why: Updating key 1 refreshes it, so key 2 is the one evicted.
Constraints
- 1 ≤ capacity ≤ 3000
- 0 ≤ key, value ≤ 10⁴
- Up to 2 × 10⁵ calls to get and put.
How to answer it out loud
What it tests: Combining data structures to meet a strict complexity target and maintaining invariants correctly.
- 1Clarify
Confirm capacity behavior and the required O(1) get and put operations.
- 2Implement
Combine a hash map with a doubly linked list so lookup, promotion, insertion, and eviction stay constant time.
- 3Verify
Trace updates, repeated keys, capacity one, and eviction order; explain the list/map invariant.
From brute force to the best solution
| Approach | Idea | Time | Space |
|---|---|---|---|
| List ordered by recency | Keep keys in an array; move a key to the end on every use. | O(n) per call | O(capacity) |
| Hash map + doubly linked listBest | The map finds a node in O(1); the list moves or evicts it in O(1). | O(1) per call | O(capacity) |
A hash map gives O(1) lookup but no order; a doubly linked list gives O(1) reordering but no lookup. Storing list nodes in the map gives you both.
Reference solution
Each version is self-contained and has been run against the examples and test cases on this page.
class Node: __slots__ = ("key", "val", "prev", "next") def __init__(self, key=0, val=0): self.key, self.val = key, val self.prev = self.next = Noneclass LRUCache: def __init__(self, capacity: int): self.capacity = capacity self.nodes = {} # key -> Node self.head, self.tail = Node(), Node() # sentinels: head.next = LRU, tail.prev = MRU self.head.next, self.tail.prev = self.tail, self.head def _remove(self, node: Node) -> None: node.prev.next, node.next.prev = node.next, node.prev def _append(self, node: Node) -> None: # insert just before tail = most recent node.prev, node.next = self.tail.prev, self.tail self.tail.prev.next = node self.tail.prev = node def get(self, key: int) -> int: node = self.nodes.get(key) if node is None: return -1 self._remove(node) self._append(node) return node.val def put(self, key: int, value: int) -> None: node = self.nodes.get(key) if node is not None: # update: refresh value and recency node.val = value self._remove(node) self._append(node) return node = Node(key, value) self.nodes[key] = node self._append(node) if len(self.nodes) > self.capacity: lru = self.head.next self._remove(lru) del self.nodes[lru.key]- Time
- O(1) per call
- Space
- O(capacity)
- Approach
- Hash map + doubly linked list
Edge cases to run before you say “done”
| Input | Expected | Why it matters |
|---|---|---|
| capacity = 1: put(1, 1), put(2, 2), get(1) | -1 | Capacity one evicts on every new key. |
| capacity = 2: put(1, 1), put(1, 5), get(1) | 5 | Updating an existing key must not evict anything. |
| capacity = 2: put(1, 1), put(2, 2), get(1), put(3, 3), get(2) | -1 | A get refreshes recency. |
Answer the main question first, then take these on one at a time:
- Why is a doubly linked list needed?
- How would you make it thread-safe?
- What happens when capacity is zero?
Check your recording or written solution against this list:
- Using an array that makes removal O(n)
- Failing to update an existing key’s recency
- Leaving evicted nodes in the map
This guide combines general interview-practice patterns with the public hiring material below. The practice prompt and coaching are PiriPiri AI editorial content, not official Amazon questions or answers.
Official sources reviewed 20 September 2026.