Implement an in-memory key-value store with per-key TTL expiration and tests

Read the full interview experience this question came from →

Quick Overview

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.

Implement an in-memory key-value store with per-key TTL expiration and tests

Company: Netflix

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

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: ```python 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: ... ``` - `put` stores `value` under `key`; the entry expires `ttl` time units after the call. - `get` returns the value if the key exists and has not expired, otherwise `None`. - `delete` removes 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 `ttl` is 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 `put` on 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 `get` distinguish a missing key from an expired one, and can a stored value be `None`? - 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. ```hint 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. ``` #### 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. ```hint 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 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. ``` #### 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. ```hint Do not wait for real time Tests that sleep are slow and flaky. Think about how a test could move time forward on demand. ``` #### 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 ### What a Strong Answer Covers - Clarifies overwrite, boundary, missing-versus-expired and concurrency semantics before coding - A short plan stated up front, followed by code that implements it directly with no unnecessary abstraction - Correct handling of outdated expiry records after overwrites and deletes - Complexity analysis, and at least one alternative cleanup strategy with its trade-off - A coherent narration that ties each decision to a requirement, and tests that cover the edge cases ### 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?

Overview: 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.

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

|Home/Software Engineering Fundamentals/Netflix
Netflix logo
Netflix
Oct 14, 2026
mediumSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
0
0

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: ...
  • put stores value under key ; the entry expires ttl time units after the call.
  • get returns the value if the key exists and has not expired, otherwise None .
  • delete removes 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 ttl is 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 Guidance

  • Does put on 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 get distinguish a missing key from an expired one, and can a stored value be None ?
  • 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 Guidance

  • 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 Guidance

  • 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 Guidance

  • 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

What a Strong Answer Covers Guidance

  • Clarifies overwrite, boundary, missing-versus-expired and concurrency semantics before coding
  • A short plan stated up front, followed by code that implements it directly with no unnecessary abstraction
  • Correct handling of outdated expiry records after overwrites and deletes
  • Complexity analysis, and at least one alternative cleanup strategy with its trade-off
  • A coherent narration that ties each decision to a requirement, and tests that cover the edge cases

Follow-up Questions Guidance

  • 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?
Loading comments...