The Eviction Policy
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Coinbase
Problem
A read-through cache sits in front of a slow store and holds at most a fixed number of entries; once it is full, the entry that has gone longest without being touched is dropped to make room for a new one. Replay operations against such a cache and return one result per operation, in order.
operations[0] is always ['LRUCache', capacity], which sets the entry limit (capacity is at least 1); each later operation is either ['put', key, value], which inserts or overwrites a key, or ['get', key], which returns that key's value or -1 when the key is absent. Both reading and writing a key count as touching it, so the next eviction removes whatever key has stayed idle longest. The constructor and every put contribute None to the result list; each get contributes the value it found, or -1.
Fixed capacity. The key left idle longest is the one that goes.
Example 1
Input:
operations = [["LRUCache",2],["put",1,1],["put",2,2],["get",1],["put",3,3],["get",2]]
Output:
[null,null,null,1,null,-1]
Example 2
Input:
operations = [["LRUCache",1],["put",1,10],["get",1],["put",2,20],["get",1],["get",2]]
Output:
[null,null,10,null,-1,20]
Worked solution and explanation
What this really is
Strip away the cache framing and this is an ordering problem wearing a storage costume: at every moment you must know which key was touched least recently, and you must know it without scanning. Anyone can stash keys in a dict. The trap is recency. A get is not a read-only operation here, it reorders. Candidates who treat get as a pure lookup and then evict by insertion order pass the easy cases and quietly evict the wrong key the first time a read lands before a write.
Break down the requirements
Step 1: get returns the value and marks the key most recently used
If the key exists, move it to the back of the order and return its value. Otherwise return -1, and leave the order untouched (a miss is not a touch).
Step 2: put inserts or updates and marks the key most recently used
If the key already exists, overwrite it and move it to the back. If it is new and the cache is at capacity, evict the front (least recently used) before inserting.
Step 3: drive the cache from the operations list
The driver reads operations[0] = ['LRUCache', capacity] to construct the cache, then replays each ['put', key, value] and ['get', key]. It appends None for the constructor and each put, and the looked-up value (or -1) for each get, returning the full list.
The solution
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, key: int) -> int:
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key: int, value: int):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
return None
def the_eviction_policy(operations):
cache = None
results = []
for op in operations:
name = op[0]
if name == "LRUCache":
cache = LRUCache(op[1])
results.append(None)
elif name == "put":
results.append(cache.put(op[1], op[2]))
elif name == "get":
results.append(cache.get(op[1]))
return resultsCommon follow-up questions
- How would you implement this without OrderedDict? (Tests building a doubly-linked list with head/tail sentinels and a hash map.)
- What is the difference between LRU and LFU? (Tests eviction strategy knowledge: recency vs frequency.)
- How would you make this thread-safe? (Tests lock granularity and concurrent data structure design.)