Design variable-size LRU cache
Company: Bloomberg
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of cache design and eviction policies (LRU), management of variable-sized entries and memory budgeting, and the implementation of efficient data structures that support amortized O(1) access, updates and correct capacity accounting.
Constraints
- The first operation is always ['init', capacity] with capacity >= 0.
- size >= 0 for every set; an entry with size > capacity is rejected (set returns false).
- Keys are hashable/comparable; values are arbitrary (compared by equality in tests).
- Eviction always removes the least-recently-used entry first until used <= capacity.
- get/set must be O(1) amortized (excluding the eviction work).
Examples
Input: ([['init', 10], ['set', 'a', 'A', 3], ['set', 'b', 'B', 3], ['set', 'c', 'C', 3], ['used'], ['keys'], ['set', 'd', 'D', 3], ['keys'], ['get', 'a']],)
Expected Output: [None, True, True, True, 9, ['c', 'b', 'a'], True, ['d', 'c', 'b'], -1]
Explanation: After inserting a,b,c (3 bytes each) used=9<=10 and MRU->LRU is c,b,a. Inserting d (3 bytes) pushes used to 12>10, so the LRU 'a' is evicted, leaving d,c,b. get('a') then returns -1 because a was evicted.
Input: ([['init', 10], ['set', 'a', 'A', 4], ['set', 'b', 'B', 4], ['get', 'a'], ['set', 'c', 'C', 4], ['keys']],)
Expected Output: [None, True, True, 'A', True, ['c', 'a']]
Explanation: a and b each use 4 bytes (used=8). get('a') refreshes a to MRU, making b the LRU. Inserting c (used would be 12>10) evicts the LRU 'b', leaving c (MRU) then a.
Hints
- Pair a hashmap (key -> node) with a doubly linked list ordered most-recently-used (head) to least-recently-used (tail). The map gives O(1) lookup; the list gives O(1) move-to-front and O(1) tail eviction.
- Track a running `used` byte total. On an in-place update, adjust `used += newSize - oldSize` BEFORE touching recency, so shrink and expand are handled by the same code path.
- Centralize eviction in one helper: `while used > capacity and list is non-empty: evict tail`. Call it after every insert/update and inside resize — that single helper makes resize-below-usage fall out for free.
- Reject an oversized insert (size > capacity) early, but still remove any existing entry for that key first so the cache never holds a stale value the caller intended to overwrite.