LRU Cache with Fewer Recency Writes and Concurrent Access
AppleSoftware Engineer · OnsiteLast updated
Implement a fixed-capacity least-recently-used cache with constant-time get and put, then discuss two follow-ups: cutting the shared-state writes that every read causes when it updates recency, and making the cache safe and fast under concurrent access, including how eviction order changes.
Implement a least-recently-used (LRU) cache with a fixed capacity. It supports get(key), which returns the value stored for the key or reports a miss, and put(key, value), which inserts the key or updates its value. Both operations count as a use of the key. When inserting a new key would exceed the capacity, the cache first evicts the least recently used key.
After the base implementation, the interviewer discussed two follow-ups: reducing the write traffic that recency tracking causes, and making the cache concurrent.
Clarifying Questions
- What should
getreturn on a miss: a sentinel such as-1, a null value, or an exception? - What should happen with a capacity of 0?
- Are keys and values arbitrary objects, or fixed types such as integers?
- Is the cache used by a single thread in the base version?
Part 1 — Base implementation
Implement the cache so that get and put both run in constant time on average.
What This Part Should Cover
- Data structures that give constant-time lookup, recency update and eviction
- Correct handling of updates to existing keys, eviction order and capacity edge cases
- Clean code with the complexity of each operation
Part 2 — Reduce write throughput
In the base design, every get changes the recency order, so a read-heavy workload turns every read into a write to shared state. How would you reduce those recency writes, and what does each option cost?
What This Part Should Cover
- Why recency updates are writes, and where those writes hurt
- At least two concrete techniques that cut recency writes, with how each one works
- The effect of each technique on eviction accuracy and hit rate
Part 3 — Make it concurrent
Many threads now call get and put at the same time. Make the cache thread-safe while keeping its throughput high.
Clarifying Questions for this Part
- Must eviction follow the exact global LRU order, or is an approximate order acceptable under concurrency?
- What is the read-to-write ratio, and roughly how many threads use the cache?
What This Part Should Cover
- The correctness and the bottleneck of a single global lock
- A finer-grained scheme, and how it divides keys and capacity
- How eviction order changes once the state is split, and when that is acceptable
- How the concurrency design combines with the write reduction from Part 2
Hints
Hint 1
Two questions per operation: Every operation must find the key's entry quickly and also know which entry is the oldest. Ask whether a single structure can answer both questions in constant time.
Hint 2
How exact must recency be: Ask whether the cache must evict exactly the least recently used key, or whether a key that is nearly the least recently used would do.
Hint 3
Reads are writes too: Before reaching for a read-write lock, check whether `get` really leaves the shared state unchanged in your design.
Hint 4
Split the state: Think about what you would gain, and what you would lose, if different keys were managed by independent pieces of state.
Follow-up questions
- How would you add a time-to-live per entry on top of LRU eviction?
- A scan reads a million keys once each and flushes your hot entries. How would you protect them?
- With sixteen shards, one shard ends up holding all the hot keys. What happens, and how would you respond?
- How would you measure whether an approximate policy costs hit rate in production?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.