PracHub
QuestionsCoachesLearningGuidesInterview Prep

Quick Overview

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.

  • medium
  • Anthropic
  • Coding & Algorithms
  • Software Engineer

Implement a crash-resilient LRU cache

Company: Anthropic

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Onsite

Implement an LRU-based memoization helper with behavior similar to a standard Python LRU cache. You are given an interface like this: ```python class LRU: def __init__(self, capacity: int, persistence_path: str): ... def generate_key(self, func, *args, **kwargs): # return a deterministic, hashable cache key pass def call(self, func, *args, **kwargs): # if the result for this function call is cached, return it # otherwise compute it, cache it, and return it pass ``` Requirements: 1. Cache results of pure function calls. 2. The cache key must include the function identity and its arguments. 3. `generate_key` must handle both positional and keyword arguments. 4. Different keyword argument orders must produce the same key. 5. When the cache exceeds `capacity`, evict the least recently used entry. 6. Assume arguments and return values are serializable. Follow-up: if the process crashes and the in-memory cache is lost, how would you persist enough information to restore the cache after restart while keeping the cache correct? Describe the data you would write, when you would write it, and how recovery would work.

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

Simulate an **in-memory LRU (Least Recently Used) memoization cache** over a sequence of pure-function calls, and report each call's result together with how many real computations were actually performed. ## What to implement ```python def solution(capacity, calls): ... ``` Process the `calls` in order against a cache that can hold at most `capacity` distinct entries. For each call, either return its cached result (a **hit**) or compute it fresh and cache it (a **miss**), evicting the least recently used entry whenever the cache would exceed `capacity`. ## Input - **`capacity`** — a non-negative integer, the maximum number of entries the cache may hold. - **`calls`** — a list of calls, each a 3-tuple `(func_name, args, kwargs)`: - `func_name` — a string naming the function to invoke (see below). - `args` — a list of positional arguments. - `kwargs` — a dict of keyword arguments. ## Supported functions `func_name` is always one of: | Name | Signature | Result | |------|-----------|--------| | `'add'` | `add(a, b)` | `a + b` | | `'mul'` | `mul(a, b)` | `a * b` | | `'pow'` | `pow(base, exp)` | `base ** exp` | | `'affine'` | `affine(x, scale=1, bias=0)` | `scale * x + bias` | Every call's `args`/`kwargs` are valid for the named function (so `scale` and `bias` may be passed positionally or by keyword, or omitted to use their defaults). ## Cache key A cache entry is identified by the combination of: 1. the **function name**, 2. the **positional arguments** (`args`, in order), and 3. the **keyword arguments** (`kwargs`). **Keyword order must not matter.** Two calls with the same keyword pairs in a different order refer to the *same* cache entry — e.g. `affine(5, scale=2, bias=1)` and `affine(5, bias=1, scale=2)` share one entry. ## Per-call behavior For each call, in order: - **Hit** — if its key is already in the cache: return the cached value and mark that entry as **most recently used** (do **not** recompute). - **Miss** — otherwise: invoke the function to compute the value, count this as one real computation, and return the value. Then, **only if `capacity > 0`**, store the value under its key as the most recently used entry; if this makes the cache size exceed `capacity`, **evict the least recently used entry**. Note that when `capacity == 0`, nothing is ever stored, so every call is a miss and is recomputed. ## Output Return a tuple `(results, computed_count)` where: - **`results`** — the list of returned values, one per call, in call order. - **`computed_count`** — the total number of misses, i.e. the number of times a value was actually computed (cache hits are not counted). ## 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

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

  1. Use a canonical key like (func_name, tuple(args), tuple(sorted(kwargs.items()))).
  2. 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

Reconstruct the exact state of an **LRU cache** after a crash by replaying its **write-ahead journal**. ## Background The cache persists durably: before any operation is considered successful, it appends a journal record describing that operation. After a crash, only a **prefix** of the journal survived (passed to you as `journal`). Replaying that surviving prefix in order, with LRU eviction, reproduces the exact recovered cache state. Implement: ```python def solution(capacity, journal): ... ``` ## Input - **`capacity`** — the maximum number of entries the cache may hold. - **`journal`** — a list of records, already in durable (chronological) order. Each record is a tuple/list of one of two forms: - **PUT:** `('PUT', func_name, args, kwargs, value)` — the computed `value` for this key is now stored, and the key becomes the **most-recently-used (MRU)** entry. - **HIT:** `('HIT', func_name, args, kwargs)` — an existing key was read, so it becomes the **MRU** entry. In every record, `func_name` is a string, `args` is a **list**, and `kwargs` is a **dict**. ## Canonical key Each record identifies a cache entry by a **canonical key** built as: ```python (func_name, tuple(args), tuple(sorted(kwargs.items()))) ``` Sorting the `kwargs` items by name means different keyword-argument orders map to the **same** key (e.g. `{'scale': 2, 'bias': 1}` and `{'bias': 1, 'scale': 2}` are identical). ## Replay rules Process records in order, maintaining LRU recency: - **PUT:** Store the value under the canonical key and mark that key as **MRU**. If this makes the number of stored entries exceed `capacity`, evict the **least-recently-used (LRU)** entry. - **HIT:** If the canonical key is **currently present**, mark it as **MRU**. If the key is **not present** during replay, **ignore** the record (no insertion, no error). ## Output Return the recovered cache contents ordered from **most-recently-used to least-recently-used**, as a list of `(canonical_key, value)` pairs, where `canonical_key` is the tuple described above. ## Edge cases - If `capacity` is `0`, no entry can ever be stored — return an empty list. - If `journal` is empty, return an empty list. ## Constraints - `0 <= capacity <= 100000` - `0 <= len(journal) <= 100000` - Each record's operation is either `'PUT'` or `'HIT'`. - All argument values and stored values are integers. - Records are already in durable journal order.

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

  1. Replay the journal exactly in order; a durable log is only as good as its replay rules.
  2. Use the same normalized key for both 'PUT' and 'HIT', and keep LRU order in an OrderedDict-style structure.
Last updated: Apr 19, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,500+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • AI Coding Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • In-Memory Key-Value Database with Nested Transactions - Anthropic (medium)
  • Maximum-Length Unique-Character Subset - Anthropic (medium)
  • Banking System Simulation - Anthropic (medium)
  • LRU Cache - Anthropic (medium)
  • Account Balance with Expiring Grants - Anthropic (medium)