Design a Fixed-Capacity Least-Recently-Used Cache
Company: xAI
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Onsite
Quick Answer: This question evaluates a candidate's understanding of data structure design and algorithmic complexity, specifically implementing a fixed-capacity least-recently-used (LRU) cache that maintains recency ordering and supports O(1) average-time get and put operations in the Coding & Algorithms category.
Constraints
- 1 <= capacity <= 10^4
- 0 <= key, value <= 10^9
- Up to 10^5 total calls to get and put
- get and put must each run in O(1) average time
- Do not use a built-in access-ordered map (OrderedDict / lru_cache / access-ordered LinkedHashMap) as the core structure
Examples
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]])
Expected Output: [1, -1, -1, 3, 4]
Explanation: The spec example (capacity 2). get(1) hits (1), then put(3,3) evicts key 2 so get(2)=-1; put(4,4) evicts key 1 so get(1)=-1; get(3)=3 and get(4)=4 remain.
Input: (['LRUCache','put','get','put','get','get'], [[1],[1,1],[1],[2,2],[1],[2]])
Expected Output: [1, -1, 2]
Explanation: Capacity 1 edge case. put(1,1) then get(1)=1; put(2,2) evicts key 1 (the only entry), so get(1)=-1 and get(2)=2.
Input: (['LRUCache','put','put','put','get','get'], [[2],[1,1],[2,2],[1,10],[1],[2]])
Expected Output: [10, 2]
Explanation: Updating an existing key must refresh recency without evicting. put(1,10) overwrites key 1's value and makes it most recently used; both keys survive, so get(1)=10 and get(2)=2.
Input: (['LRUCache','put','put','get','put','get','get'], [[2],[1,1],[2,2],[1],[3,3],[1],[3]])
Expected Output: [1, 1, 3]
Explanation: Touching the current LRU entry protects it. get(1) makes key 1 most recently used, so the later put(3,3) evicts key 2 instead; get(1)=1 and get(3)=3.
Input: (['LRUCache','get','put','get'], [[1],[0],[0,5],[0]])
Expected Output: [-1, 5]
Explanation: get on an empty cache returns -1. After put(0,5), get(0)=5 (key and value of 0 are valid boundary values).
Hints
- Combine a hash map (key -> node) for O(1) lookup with a doubly linked list that keeps entries ordered by recency; the head end is most recently used, the tail end is least recently used.
- Use two sentinel nodes (a dummy head and dummy tail) so insertion and removal never have to special-case the ends of the list — every real node always has a non-null prev and next.
- On both a get hit and a put on an existing key, unlink the node from its current position and re-insert it right behind the head (most recently used). On eviction, remove the node just before the tail and delete its key from the map.
- When updating an existing key in put, refresh recency but do NOT evict — the size did not grow. Only a brand-new key at full capacity triggers an eviction.