Design and Implement an LRU Cache
Company: Uber
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates a candidate's ability to design and implement an LRU cache data structure with O(1) time complexity for both reads and writes. It tests practical knowledge of combining hash maps with doubly linked lists, a core data structures and algorithms topic frequently used to assess low-level design and implementation skills in software engineering interviews.
Constraints
- 1 <= capacity <= 3000
- 0 <= key <= 10^4
- 0 <= value <= 10^5
- At most 2 * 10^5 total get and put calls.
- Both get and put must run in average O(1) time per call.
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: [None, None, None, 1, None, -1, None, -1, 3, 4]
Explanation: The worked example. capacity=2. get(1) makes 1 MRU; put(3,3) evicts 2; put(4,4) evicts 1; remaining {3,4}.
Input: (["LRUCache", "put", "get", "put", "get", "get"], [[1], [2, 1], [2], [3, 2], [2], [3]])
Expected Output: [None, None, 1, None, -1, 2]
Explanation: Capacity 1. put(2,1) then get(2)->1. put(3,2) evicts key 2, so get(2)->-1 and get(3)->2.
Input: (["LRUCache", "get"], [[1], [0]])
Expected Output: [None, -1]
Explanation: Edge case: get on an empty cache returns -1.
Input: (["LRUCache", "put", "put", "get", "get"], [[2], [1, 10], [1, 20], [1], [2]])
Expected Output: [None, None, None, 20, -1]
Explanation: Updating an existing key (put(1,20)) overwrites the value without inserting a new entry, so no eviction; get(1)->20, get(2)->-1.
Input: (["LRUCache", "put", "put", "put", "get", "get", "get"], [[2], [1, 1], [2, 2], [3, 3], [1], [2], [3]])
Expected Output: [None, None, None, None, -1, 2, 3]
Explanation: Three inserts into capacity 2: put(3,3) evicts the LRU key 1, so get(1)->-1, get(2)->2, get(3)->3.
Hints
- A hash map alone gives O(1) lookup but no notion of recency order; pair it with something that tracks usage order.
- Combine a hash map with a doubly linked list (or Python's OrderedDict): the map gives O(1) access, the list gives O(1) reordering and O(1) eviction of the least recently used end.
- On every get and every put, move the touched key to the most-recently-used end. When size exceeds capacity after a put, remove the key at the least-recently-used end.