Make a Dict-Based Memoization Cache Safe for Concurrent Threads

Read the full interview experience this question came from →

Quick Overview

A memoization cache built on a plain Python dictionary wraps an expensive function and is now called from many threads at once. The question asks what goes wrong and how to fix it, testing race-condition analysis, lock scope and its throughput cost, duplicate-work prevention, and exception handling for waiting callers.

Make a Dict-Based Memoization Cache Safe for Concurrent Threads

Company: Mercor

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

A slow, expensive function is wrapped in a module-level dictionary that memoizes its results: ```python cache = {} def compute(x): if x in cache: return cache[x] result = expensive(x) cache[x] = result return result ``` `compute` is now called from many threads at the same time. What problems does that cause, and how would you change the code? ### Clarifying Questions - Is `expensive(x)` deterministic and free of side effects, or is running it twice for the same `x` harmful (for example, it calls a paid external service or writes data)? - Can `expensive(x)` raise, and should a failure be remembered or retried on the next call? - Is the set of distinct inputs bounded, or can the cache grow without limit? - Which runtime is this: CPython with its global interpreter lock, a free-threaded build, or a language whose plain hash map is not safe for concurrent writers? - Is `expensive` mostly waiting on I/O, or mostly using the CPU? ### Part 1 — What goes wrong Explain what can happen when several threads call `compute` concurrently, using concrete interleavings. ```hint Interleave two threads Step two threads through `compute` with the same new key, one line at a time, and look for the window between the check and the write. Then repeat with two different keys. ``` #### What This Part Should Cover - The check-then-act window, and what happens when several callers miss the same key together - Which guarantees the runtime's dictionary does and does not give under concurrent access - The consequences when `expensive` is slow, non-deterministic, or has side effects ### Part 2 — Fix it and choose the lock scope Make `compute` safe for concurrent callers. Compare at least two places to put the lock, and say what each one costs when many threads ask for different keys versus the same key. ```hint Two jobs for one lock A lock here could do two separate jobs: protect the dictionary, and stop the same key from being computed twice. Ask whether one lock has to do both, and what it blocks while `expensive` runs. ``` #### What This Part Should Cover - Whether each candidate prevents duplicate computation, and whether it keeps the dictionary consistent - What each lock scope serializes, and the effect on throughput - What happens to waiting callers when the computation raises, so that a failure neither poisons the cache nor leaves threads blocked ### What a Strong Answer Covers - A concrete interleaving that demonstrates the race, rather than a general claim that the code "is not thread-safe" - The difference between the dictionary's own safety and the logical race on a key - A fix that computes each key once without holding a global lock while `expensive` runs - Exception handling, memory growth and deadlock risks of the chosen design - Working code, with a clear statement of what it does and does not guarantee ### Follow-up Questions - How would you add a size limit or expiry without evicting an entry that other threads are waiting on? - If `expensive(x)` itself calls `compute` for other keys, can your design deadlock? - How would the design change if the callers were separate processes, or separate machines sharing one cache?

Overview: A memoization cache built on a plain Python dictionary wraps an expensive function and is now called from many threads at once. The question asks what goes wrong and how to fix it, testing race-condition analysis, lock scope and its throughput cost, duplicate-work prevention, and exception handling for waiting callers.

Read the full Mercor Software Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/Mercor
Mercor logo
Mercor
May 31, 2026
mediumSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
1
0

A slow, expensive function is wrapped in a module-level dictionary that memoizes its results:

cache = {}

def compute(x):
    if x in cache:
        return cache[x]
    result = expensive(x)
    cache[x] = result
    return result

compute is now called from many threads at the same time. What problems does that cause, and how would you change the code?

Clarifying Questions Guidance

  • Is expensive(x) deterministic and free of side effects, or is running it twice for the same x harmful (for example, it calls a paid external service or writes data)?
  • Can expensive(x) raise, and should a failure be remembered or retried on the next call?
  • Is the set of distinct inputs bounded, or can the cache grow without limit?
  • Which runtime is this: CPython with its global interpreter lock, a free-threaded build, or a language whose plain hash map is not safe for concurrent writers?
  • Is expensive mostly waiting on I/O, or mostly using the CPU?

Part 1 — What goes wrong

Explain what can happen when several threads call compute concurrently, using concrete interleavings.

What This Part Should Cover Guidance

  • The check-then-act window, and what happens when several callers miss the same key together
  • Which guarantees the runtime's dictionary does and does not give under concurrent access
  • The consequences when expensive is slow, non-deterministic, or has side effects

Part 2 — Fix it and choose the lock scope

Make compute safe for concurrent callers. Compare at least two places to put the lock, and say what each one costs when many threads ask for different keys versus the same key.

What This Part Should Cover Guidance

  • Whether each candidate prevents duplicate computation, and whether it keeps the dictionary consistent
  • What each lock scope serializes, and the effect on throughput
  • What happens to waiting callers when the computation raises, so that a failure neither poisons the cache nor leaves threads blocked

What a Strong Answer Covers Guidance

  • A concrete interleaving that demonstrates the race, rather than a general claim that the code "is not thread-safe"
  • The difference between the dictionary's own safety and the logical race on a key
  • A fix that computes each key once without holding a global lock while expensive runs
  • Exception handling, memory growth and deadlock risks of the chosen design
  • Working code, with a clear statement of what it does and does not guarantee

Follow-up Questions Guidance

  • How would you add a size limit or expiry without evicting an entry that other threads are waiting on?
  • If expensive(x) itself calls compute for other keys, can your design deadlock?
  • How would the design change if the callers were separate processes, or separate machines sharing one cache?
Loading comments...