Implement a crash-resilient LRU cache
Company: Anthropic
Role: Software Engineer
Category: Coding & Algorithms
Interview Round: Onsite
Quick Answer: This question evaluates knowledge of cache design, least-recently-used (LRU) eviction policies, deterministic memoization key construction for function identity and arguments, and persistence mechanisms for crash resilience in the Coding & Algorithms domain.
Part 1: Implement an in-memory LRU-based memoization helper
Constraints
- 0 <= capacity <= 100000
- 0 <= len(calls) <= 100000
- func_name is one of 'add', 'mul', 'pow', 'affine'
- All provided calls are valid for the named function
- All argument values are integers and keyword arguments are hashable
Examples
Input: (2, [('add', [1, 2], {}), ('add', [1, 2], {})])
Expected Output: ([3, 3], 1)
Explanation: The second call is a cache hit.
Input: (2, [('affine', [5], {'scale': 2, 'bias': 1}), ('affine', [5], {'bias': 1, 'scale': 2})])
Expected Output: ([11, 11], 1)
Explanation: Keyword argument order should not change the cache key.
Input: (2, [('add', [1, 2], {}), ('mul', [1, 2], {}), ('add', [1, 2], {}), ('pow', [2, 3], {}), ('mul', [1, 2], {})])
Expected Output: ([3, 2, 3, 8, 2], 4)
Explanation: After 'pow' is inserted, the least recently used entry is evicted. 'add' and 'mul' use different cache keys even with the same positional arguments.
Input: (3, [])
Expected Output: ([], 0)
Explanation: Edge case: no calls.
Input: (0, [('add', [1, 1], {}), ('add', [1, 1], {})])
Expected Output: ([2, 2], 2)
Explanation: Edge case: capacity 0 means nothing is stored, so every call is recomputed.
Hints
- Use a canonical key like (func_name, tuple(args), tuple(sorted(kwargs.items()))).
- A hash map plus an OrderedDict-style structure makes LRU updates efficient.
Part 2: Recover a crash-resilient LRU cache from a write-ahead journal
Constraints
- 0 <= capacity <= 100000
- 0 <= len(journal) <= 100000
- Each record type is either 'PUT' or 'HIT'
- All argument values and stored values are integers
- Records are already in durable journal order
Examples
Input: (2, [('PUT', 'add', [1, 2], {}, 3), ('PUT', 'mul', [2, 3], {}, 6), ('HIT', 'add', [1, 2], {})])
Expected Output: [(('add', (1, 2), ()), 3), (('mul', (2, 3), ()), 6)]
Explanation: The hit on 'add' makes it the most recently used entry.
Input: (2, [('PUT', 'add', [1, 2], {}, 3), ('PUT', 'mul', [2, 3], {}, 6), ('PUT', 'pow', [2, 5], {}, 32), ('HIT', 'add', [1, 2], {})])
Expected Output: [(('pow', (2, 5), ()), 32), (('mul', (2, 3), ()), 6)]
Explanation: When 'pow' is inserted, 'add' is evicted as the least recently used entry. The later hit on 'add' is ignored because that key is no longer present.
Input: (2, [('PUT', 'affine', [5], {'scale': 2, 'bias': 1}, 11), ('PUT', 'add', [1, 1], {}, 2), ('HIT', 'affine', [5], {'bias': 1, 'scale': 2})])
Expected Output: [(('affine', (5,), (('bias', 1), ('scale', 2))), 11), (('add', (1, 1), ()), 2)]
Explanation: Keyword argument order is normalized during recovery, so the hit matches the existing 'affine' entry.
Input: (0, [('PUT', 'add', [1, 2], {}, 3), ('HIT', 'add', [1, 2], {})])
Expected Output: []
Explanation: Edge case: capacity 0 means the recovered cache is empty.
Input: (3, [])
Expected Output: []
Explanation: Edge case: an empty durable journal recovers an empty cache.
Hints
- Replay the journal exactly in order; a durable log is only as good as its replay rules.
- Use the same normalized key for both 'PUT' and 'HIT', and keep LRU order in an OrderedDict-style structure.