Implement an LRU Cache
Company: Shopify
Role: Data Scientist
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates competence in designing efficient cache mechanisms and mastery of data-structure strategies to support average O(1) get and put operations.
Constraints
- 1 <= capacity
- Keys and values are integers (LeetCode 146 uses 0 <= key, value <= 10^4 / 10^5).
- operations[0] is always "LRUCache" (the constructor).
- Each non-constructor operation is either "get" (args [key]) or "put" (args [key, value]).
- get and put must run in average O(1) time.
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: Canonical LeetCode 146 trace with capacity 2. get(1)=1 promotes key 1; put(3,3) evicts key 2 -> get(2)=-1; put(4,4) evicts key 1 -> get(1)=-1; key 3 and 4 remain -> get(3)=3, get(4)=4.
Input: (['LRUCache', 'put', 'put', 'get', 'put', 'get'], [[2], [1, 1], [2, 2], [1], [3, 3], [2]])
Expected Output: [None, None, None, 1, None, -1]
Explanation: The example from the prompt: get(1)=1 makes key 1 MRU, so put(3,3) evicts the now-least-recently-used key 2, giving get(2)=-1.
Input: (['LRUCache', 'put', 'put', 'get', 'get'], [[1], [1, 10], [2, 20], [1], [2]])
Expected Output: [None, None, None, -1, 20]
Explanation: Capacity-1 edge case: putting key 2 immediately evicts key 1, so get(1)=-1 and get(2)=20.
Input: (['LRUCache', 'get'], [[2], [0]])
Expected Output: [None, -1]
Explanation: Edge case: get on an empty cache for a key that was never inserted returns -1.
Input: (['LRUCache', 'put', 'put', 'get'], [[2], [5, 100], [5, 200], [5]])
Expected Output: [None, None, None, 200]
Explanation: Re-putting an existing key updates its value (and marks it MRU) without changing the size; get(5) returns the updated 200.
Hints
- Combine a hash map (key -> node) for O(1) lookup with a structure that maintains usage order so the least-recently-used entry is found in O(1).
- A doubly linked list with sentinel head/tail nodes lets you move a node to the front and pop from the back in O(1). Python's OrderedDict (move_to_end / popitem) and Java's LinkedHashMap in access-order mode encapsulate exactly this.
- On get: if the key is missing return -1; otherwise mark it most-recently-used before returning its value.
- On put: update or insert, mark it most-recently-used, then if size exceeds capacity evict the least-recently-used entry (the back of the list / first inserted ordered entry).