Build a Fixed-Capacity Least-Recently-Used Cache with Constant-Time Get and Put

Quick Overview

Implement a fixed-capacity least-recently-used cache whose get and put both run in constant average time, explaining each design decision from first principles in a discussion-heavy round. Tests data structure choice, eviction and update semantics, edge cases, complexity analysis and clear technical communication.

Build a Fixed-Capacity Least-Recently-Used Cache with Constant-Time Get and Put

Company: Microsoft

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: easy

Interview Round: Technical Screen

Implement a least-recently-used (LRU) cache that holds at most `capacity` entries. It supports two operations: - `get(key)`: return the value stored for `key` and mark `key` as the most recently used entry. - `put(key, value)`: insert `key` with `value`, or update the value if `key` is already present, and mark `key` as the most recently used entry. If the cache now holds more than `capacity` entries, evict the least recently used one. Both operations should run in constant average time, independent of how many entries the cache holds. The round was mostly conversation rather than typing: the problem was taken apart in detail from the very beginning, and comparatively little code was written. Expect to explain what "least recently used" means operationally, why each data structure is there, and how every operation keeps them consistent, before and while you code. ```text capacity = 2 put(1, "a") cache holds 1 put(2, "b") cache holds 1, 2 get(1) -> "a" 1 is now the most recently used put(3, "c") evicts 2, the least recently used get(2) -> missing ``` ```hint Where the time goes Try a hash map on its own, then an ordered list on its own, and name the operation that stops being constant time in each. The fix has to let the two structures reach each other directly. ``` ```hint Unlink in place Promoting an entry from the middle of the recency order must not require a search. Ask what each element of the order needs to know to remove itself, and how to avoid special cases when it is first or last. ``` ### Clarifying Questions - What should `get` return for a key that is not in the cache: a sentinel value, `None`, or an error? - Does updating the value of an existing key count as a use, so that it becomes the most recently used entry? - Is `capacity` fixed at construction, and what should happen if it is zero or negative? - Will the cache be called from several threads at once? - Are keys arbitrary hashable values, and is capacity counted in entries rather than bytes? ### What a Strong Answer Covers - Pins down the semantics before coding: what counts as a use, the missing-key result, updates of existing keys, and invalid capacities - Explains from first principles why neither a hash map nor an ordered list alone meets the time bound, and arrives at a combined structure with a stated invariant that links the two - Correct `get`, `put` and eviction, including updating an existing key without creating a duplicate and removing an evicted entry from both structures - Time per operation and memory overhead per entry, including why the bound is average-case rather than worst-case - A trace and test cases: capacity 1, repeated puts of one key, a get after eviction, an update while the cache is full - Clear, structured explanations when every step is questioned, with code that matches what was described ### Follow-up Questions - Make the cache safe for many concurrent threads. Why does `get` need more than a read lock, and how would you reduce contention? - Capacity is now a byte budget and values have different sizes. What changes in `put`? - Add a time-to-live for each entry. How do expired entries interact with least-recently-used eviction? - A workload occasionally reads a large set of keys exactly once, in sequence. What does that do to the hit rate, and how could the eviction policy resist it?

Overview: Implement a fixed-capacity least-recently-used cache whose get and put both run in constant average time, explaining each design decision from first principles in a discussion-heavy round. Tests data structure choice, eviction and update semantics, edge cases, complexity analysis and clear technical communication.

Microsoft logo
Microsoft
Oct 14, 2026
easySoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
1
0

Implement a least-recently-used (LRU) cache that holds at most capacity entries. It supports two operations:

  • get(key) : return the value stored for key and mark key as the most recently used entry.
  • put(key, value) : insert key with value , or update the value if key is already present, and mark key as the most recently used entry. If the cache now holds more than capacity entries, evict the least recently used one.

Both operations should run in constant average time, independent of how many entries the cache holds.

The round was mostly conversation rather than typing: the problem was taken apart in detail from the very beginning, and comparatively little code was written. Expect to explain what "least recently used" means operationally, why each data structure is there, and how every operation keeps them consistent, before and while you code.

capacity = 2
put(1, "a")             cache holds 1
put(2, "b")             cache holds 1, 2
get(1)      -> "a"      1 is now the most recently used
put(3, "c")             evicts 2, the least recently used
get(2)      -> missing

Clarifying Questions Guidance

  • What should get return for a key that is not in the cache: a sentinel value, None , or an error?
  • Does updating the value of an existing key count as a use, so that it becomes the most recently used entry?
  • Is capacity fixed at construction, and what should happen if it is zero or negative?
  • Will the cache be called from several threads at once?
  • Are keys arbitrary hashable values, and is capacity counted in entries rather than bytes?

What a Strong Answer Covers Guidance

  • Pins down the semantics before coding: what counts as a use, the missing-key result, updates of existing keys, and invalid capacities
  • Explains from first principles why neither a hash map nor an ordered list alone meets the time bound, and arrives at a combined structure with a stated invariant that links the two
  • Correct get , put and eviction, including updating an existing key without creating a duplicate and removing an evicted entry from both structures
  • Time per operation and memory overhead per entry, including why the bound is average-case rather than worst-case
  • A trace and test cases: capacity 1, repeated puts of one key, a get after eviction, an update while the cache is full
  • Clear, structured explanations when every step is questioned, with code that matches what was described

Follow-up Questions Guidance

  • Make the cache safe for many concurrent threads. Why does get need more than a read lock, and how would you reduce contention?
  • Capacity is now a byte budget and values have different sizes. What changes in put ?
  • Add a time-to-live for each entry. How do expired entries interact with least-recently-used eviction?
  • A workload occasionally reads a large set of keys exactly once, in sequence. What does that do to the hit rate, and how could the eviction policy resist it?