Implement an in-memory key-value store with per-key TTL expiration and tests
NetflixSoftware Engineer · Technical ScreenLast updated
A coding interview question that asks you to implement an in-memory key-value store whose entries expire after a per-key time-to-live, explain the design as you go, and test it. It tests expiry semantics, overwrite handling, reclaiming memory from keys that are never read again, complexity trade-offs and deterministic tests with a controllable clock.
In a phone screen, implement an in-memory key-value store in which every entry expires after its own time-to-live (TTL). Implement the usual operations, explain your design and its trade-offs as you go, and finish by writing test cases and running them.
The interviewer evaluates more than whether the code runs: how you clarify the requirements and break the problem down, whether the code is concise and direct, and whether your explanation stays coherent from the first idea to the last test.
Unless the interviewer changes it, assume this interface:
class TTLStore:
def put(self, key: str, value: object, ttl: float) -> None: ...
def get(self, key: str) -> object | None: ...
def delete(self, key: str) -> bool: ...
putstoresvalueunderkey; the entry expiresttltime units after the call.getreturns the value if the key exists and has not expired, otherwiseNone.deleteremoves the key and reports whether a live entry was removed.
Constraints and Clarifications
- One process, data in memory only; no persistence or replication.
- Keys are strings, values are arbitrary objects, and
ttlis a positive number in the same unit as the clock you use. - The exact behavior at the moment an entry reaches its expiry time is not specified. Define it.
Clarifying Questions
- Does
puton an existing key replace its TTL as well as its value? - Is a TTL required on every
put, or can some entries live forever? - Should
getdistinguish a missing key from an expired one, and can a stored value beNone? - Are other operations needed, such as counting live keys or extending a key's TTL?
- Will several threads use the store at the same time?
- Must memory held by expired entries be reclaimed even if those keys are never read again?
Part 1 — Core operations and expiry semantics
Implement put, get and delete so that an expired entry is never returned.
What This Part Should Cover
- The state stored per entry, and the exact expiry boundary
- Overwrite semantics, including what happens to the previous TTL
- The choice of time source, and why
- The time complexity of each operation
Part 2 — Removing expired entries
A key that is written once and never read again stays in memory forever if expiry is checked only inside get. Add a mechanism that removes expired entries without scanning the whole store, and explain how it interacts with overwrites and deletes.
What This Part Should Cover
- The cleanup data structure, and when cleanup runs (inside each operation, in a background thread, or both)
- Handling outdated expiry records left by overwrites and deletes
- Amortized cost, and worst-case pauses when many keys expire together
- Memory growth from outdated records, and how to bound it
Part 3 — Tests
Write test cases that show the store works, including its time-dependent behavior, and that run quickly and deterministically.
What This Part Should Cover
- How tests control time
- Boundary cases: exactly at expiry, overwrite before expiry, delete followed by reinsert, missing keys
- A test proving that cleanup frees entries that are never read
Hints
Hint 1
Record what you will compare against: Decide what to store at `put` time so that a later `get` can tell whether the entry is still valid with a single comparison, and decide where the current time comes from.
Hint 2
Which key expires next?: Look for a structure that can always tell you the earliest pending expiry cheaply, even as new keys keep arriving.
Hint 3
Overwrites leave traces: When a key is overwritten or deleted, its old expiry is still recorded somewhere. Decide how cleanup recognizes that record as outdated instead of deleting the newer value.
Hint 4
Do not wait for real time: Tests that sleep are slow and flaky. Think about how a test could move time forward on demand.
Follow-up questions
- Make the store safe for concurrent callers. Where do the locks go, and does a background cleanup thread change your answer?
- A million keys expire in the same second. How do you keep a single `put` from pausing while all of them are removed?
- Add a maximum number of entries with least-recently-used eviction alongside the TTL. How do the two mechanisms interact?
- What alternatives exist to the cleanup structure you chose, such as a timing wheel or periodic random sampling of keys, and when would each one win?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.