Design and implement an LRU cache
Company: NVIDIA
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates knowledge of data structures, algorithmic design, API design, and memory/resource management required to implement an efficient eviction-based cache (LRU) that supports constant-time operations.
Constraints
- 0 <= capacity
- Each operation is either ["put", key, value] or ["get", key]
- Keys and values are integers
- get and put must each run in O(1) average time
- When capacity == 0, the cache stores nothing: every put is a no-op and every get returns -1
Examples
Input: (2, [["put", 1, 1], ["put", 2, 2], ["get", 1], ["put", 3, 3], ["get", 2], ["put", 4, 4], ["get", 1], ["get", 3], ["get", 4]])
Expected Output: [None, None, 1, None, -1, None, -1, 3, 4]
Explanation: Canonical trace. put(3) evicts key 2 (LRU after get(1) touched key 1), so get(2) -> -1. put(4) evicts key 1, so get(1) -> -1; keys 3 and 4 remain.
Input: (2, [["put", 2, 1], ["put", 2, 2], ["get", 2], ["put", 1, 1], ["put", 4, 1], ["get", 2]])
Expected Output: [None, None, 2, None, None, -1]
Explanation: Updating key 2 then getting it returns the updated value 2. After put(1) and put(4) the cache is full and key 2 was least recently used, so it is evicted: get(2) -> -1.
Input: (0, [["put", 1, 1], ["get", 1], ["put", 2, 2], ["get", 2]])
Expected Output: [None, -1, None, -1]
Explanation: Capacity 0 edge case: the cache never stores anything, so every put is a no-op and every get returns -1.
Input: (1, [["put", 1, 1], ["get", 1], ["put", 2, 2], ["get", 1], ["get", 2]])
Expected Output: [None, 1, None, -1, 2]
Explanation: Capacity 1: put(2) evicts key 1, so get(1) -> -1 and get(2) -> 2.
Input: (2, [["get", 5]])
Expected Output: [-1]
Explanation: get on an empty cache returns the sentinel -1.
Input: (3, [["put", 1, 10], ["put", 2, 20], ["put", 3, 30], ["get", 1], ["put", 4, 40], ["get", 2], ["get", 3], ["get", 4], ["get", 1]])
Expected Output: [None, None, None, 10, None, -1, 30, 40, 10]
Explanation: Capacity 3 fills with keys 1,2,3. get(1) promotes key 1. put(4) evicts the LRU key 2 -> get(2) -1; keys 3,4,1 survive.
Input: (2, [["put", 1, 1], ["put", 1, 5], ["get", 1], ["put", 2, 2], ["put", 3, 3], ["get", 1]])
Expected Output: [None, None, 5, None, None, -1]
Explanation: Re-putting key 1 updates and promotes it (get returns 5). After put(2) and put(3) the cache exceeds capacity; key 1 was least recently used relative to 2, so put(3) evicts key 1: get(1) -> -1.
Hints
- Combine a hash map (for O(1) key lookup) with a doubly linked list (for O(1) reordering and eviction). The map points each key to its list node.
- On get, if the key exists, move its node to the most-recently-used end and return its value; otherwise return -1.
- On put, update-and-promote if the key exists; otherwise insert at the MRU end and, if size now exceeds capacity, evict the node at the LRU end.
- Python's collections.OrderedDict gives you both behaviors for free: move_to_end(key) promotes, and popitem(last=False) evicts the least recently used entry.
- Guard the capacity == 0 case so put never inserts and the cache stays empty.