Implement a TTL LRU Cache
Company: Nimble
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of in-memory cache design, including per-entry TTL expiration, LRU eviction, and the data structure and complexity trade-offs required to achieve O(1) average operations.
Constraints
- 0 <= capacity <= 200000
- 1 <= len(operations) <= 200000
- Operation timestamps are in non-decreasing order
- 0 < default_ttl <= 10^9 and any provided ttl satisfies 0 < ttl <= 10^9
- 0 <= key <= 2^32 - 1
- The total length of all value strings is at most 200000
Examples
Input: (2, 5, [('put', 1, 'x', 'a'), ('get', 2, 'x'), ('get', 6, 'x'), ('size', 6)])
Expected Output: ['a', None, 0]
Explanation: `x` is written at time 1 with expiry 6. It is still valid at time 2, but expired at time 6, so the final size is 0.
Input: (2, 5, [('put', 1, 'x', 'a'), ('get', 2, 'x'), ('put', 3, 'y', 'b', 1), ('get', 4, 'y'), ('get', 4, 'x'), ('put', 5, 'z', 'c'), ('get', 5, 'z'), ('size', 5)])
Expected Output: ['a', None, 'a', 'c', 2]
Explanation: `y` has TTL 1, so it expires at time 4. `x` remains valid, `z` is added later, and the cache contains `x` and `z` at the end.
Input: (2, 10, [('put', 1, 'a', 1), ('put', 2, 'b', 2), ('get', 3, 'a'), ('put', 4, 'c', 3), ('get', 5, 'b'), ('get', 5, 'a'), ('get', 5, 'c'), ('size', 5)])
Expected Output: [1, None, 1, 3, 2]
Explanation: Accessing `a` makes it most recently used, so when `c` is inserted into a full cache, `b` is the least recently used key and gets evicted.
Input: (2, 2, [('put', 1, 'a', 10), ('put', 2, 'b', 20, 10), ('put', 3, 'c', 30, 10), ('get', 3, 'a'), ('get', 3, 'b'), ('get', 3, 'c'), ('size', 3)])
Expected Output: [None, 20, 30, 2]
Explanation: `a` expires exactly at time 3, so it must be removed before inserting `c`. That means no unexpired key needs to be evicted.
Input: (2, 3, [('put', 1, 'a', 'old'), ('put', 2, 'b', 'keep'), ('put', 3, 'a', 'new', 5), ('put', 4, 'c', 'extra'), ('get', 4, 'b'), ('get', 4, 'a'), ('get', 7, 'a'), ('get', 8, 'a'), ('size', 8)])
Expected Output: [None, 'new', 'new', None, 0]
Explanation: Updating `a` changes its value, extends its lifetime, and makes it most recently used. `b` becomes the LRU entry and is evicted when `c` is inserted.
Input: (0, 5, [('put', 1, 'x', 'ignored'), ('get', 1, 'x'), ('size', 2)])
Expected Output: [None, 0]
Explanation: A cache with capacity 0 cannot store any entries.
Hints
- Use a hash map for direct access by key, and keep recency in a structure that can move an entry to the most-recent end in O(1).
- Expired entries are not guaranteed to be near the LRU end. A separate min-heap of expiration times lets you remove them lazily without scanning the whole cache.