Key-Value Store Persistence: Length-Prefixed Bytes, Size-Capped Chunks, Thread Safety

Quick Overview

A three-part key-value store exercise: serialize keys, values and their lengths into bytes and persist them on shutdown, split the saved data into chunks of a given maximum size, and discuss making the existing code safe for multithreaded use. It tests byte-level encoding, correct restore and concurrency reasoning.

Key-Value Store Persistence: Length-Prefixed Bytes, Size-Capped Chunks, Thread Safety

Company: OpenAI

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: hard

Interview Round: Technical Screen

Implement an in-memory key-value store whose contents survive a restart. While the program runs, the store serves reads and writes from memory. When `shutdown()` is called, it writes its contents to disk; when the store starts again, it restores exactly the same contents. The exact interface was not reported. A reasonable starting point: ```python class KVStore: def __init__(self, path: str) -> None: ... # restore from disk if a saved store exists def get(self, key: str) -> str | None: ... def set(self, key: str, value: str) -> None: ... def shutdown(self) -> None: ... # persist everything to disk ``` ### Constraints and Clarifications - Assume keys and values may contain any characters, including newlines, spaces and non-ASCII text. - In Parts 1 and 2, data is written only on `shutdown()`; nothing needs to be persisted on each `set`. ### Clarifying Questions - Are keys and values always strings, or can they be arbitrary bytes? Is there a maximum length for either? - Is there a `delete` operation, and must deletions survive a restart? - Must the store survive only a clean `shutdown()`, or also a crash at any moment? - If the saved data is missing or damaged at start-up, should the store start empty or refuse to start? ### Part 1 — Serialize keys, values and their lengths to bytes On `shutdown()`, convert every key, every value and the length of each into bytes and write them to disk. On start-up, read the bytes back and rebuild the store. ```hint Count the right thing Decide what unit each length measures and how many bytes the length itself occupies, so the reader never has to guess where a number ends or how much data follows it. ``` #### What This Part Should Cover - A byte layout for each record: how lengths are encoded (width, byte order) and what they count - Text encoding of keys and values, and byte length versus character length - An exact round trip, including empty strings and an empty store - Behavior when the saved file is missing, truncated or corrupt ### Part 2 — Split the saved data into chunks of a given size You are now also given a size. The saved data must be split into chunks no larger than that size before being written to disk. Restoring must reassemble the chunks and rebuild the same store. ```hint Reassembly needs its own information Whoever restores the store must know which chunks belong to the latest save, in what order, and whether the set is complete. Decide where that information lives and when it is written. ``` #### Clarifying Questions for this Part - Does the size limit apply to each chunk file, including any header you add to it? - May one key-value record be split across two chunks, or must every record fit inside a single chunk? What if one record is larger than the size? - Should each chunk be written as a separate file? #### What This Part Should Cover - Chunking strategy: slicing one byte stream versus packing whole records - Chunk naming, ordering, and the metadata needed for reassembly - Validation on restore: missing or damaged chunks, and leftovers from an earlier, larger save - Keeping the previous save intact if writing many chunks is interrupted ### Part 3 — Make the existing code safe for multiple threads (discussion only) The store is now used by several threads at once. Without writing code, discuss what can go wrong in the current implementation and how you would support concurrent `get`, `set` and `shutdown` calls. ```hint Shutdown is the hard case Single reads and writes are only part of the problem. Think about what `shutdown` sees if another thread calls `set` while it is serializing, and what should happen to a `set` that arrives afterwards. ``` #### What This Part Should Cover - Which operations race in the current code, and what the failures look like - Locking options (one lock, reader-writer lock, lock striping) and their trade-offs - A consistent snapshot for persistence without holding a lock during disk I/O - Behavior of calls that arrive during or after shutdown ### What a Strong Answer Covers - A precise byte format that round-trips arbitrary keys and values, with lengths measured in bytes - Restore that rejects truncated or corrupt data instead of loading part of it - Chunking that preserves order and lets the reader detect an incomplete save - A concurrency design built around a consistent snapshot and a clear shutdown cut-off - Tests for edge cases: empty strings, separator-like characters inside data, multi-byte characters, records larger than the chunk size ### Follow-up Questions - The store is too large to hold a second, encoded copy in memory during shutdown. How do you stream the save? - A crash happens halfway through `shutdown`. How do you make sure the previous save is not destroyed? - How would you make every `set` durable immediately, so that a crash at any moment loses nothing? - Most operations are reads from many threads. How does that change your locking choice, and what difference does Python's global interpreter lock make?

Overview: A three-part key-value store exercise: serialize keys, values and their lengths into bytes and persist them on shutdown, split the saved data into chunks of a given maximum size, and discuss making the existing code safe for multithreaded use. It tests byte-level encoding, correct restore and concurrency reasoning.

OpenAI logo
OpenAI
Oct 9, 2026
hardSoftware EngineerTechnical ScreenSoftware Engineering Fundamentals
9
0

Implement an in-memory key-value store whose contents survive a restart. While the program runs, the store serves reads and writes from memory. When shutdown() is called, it writes its contents to disk; when the store starts again, it restores exactly the same contents.

The exact interface was not reported. A reasonable starting point:

class KVStore:
    def __init__(self, path: str) -> None: ...        # restore from disk if a saved store exists
    def get(self, key: str) -> str | None: ...
    def set(self, key: str, value: str) -> None: ...
    def shutdown(self) -> None: ...                    # persist everything to disk

Constraints and Clarifications

  • Assume keys and values may contain any characters, including newlines, spaces and non-ASCII text.
  • In Parts 1 and 2, data is written only on shutdown() ; nothing needs to be persisted on each set .

Clarifying Questions Guidance

  • Are keys and values always strings, or can they be arbitrary bytes? Is there a maximum length for either?
  • Is there a delete operation, and must deletions survive a restart?
  • Must the store survive only a clean shutdown() , or also a crash at any moment?
  • If the saved data is missing or damaged at start-up, should the store start empty or refuse to start?

Part 1 — Serialize keys, values and their lengths to bytes

On shutdown(), convert every key, every value and the length of each into bytes and write them to disk. On start-up, read the bytes back and rebuild the store.

What This Part Should Cover Guidance

  • A byte layout for each record: how lengths are encoded (width, byte order) and what they count
  • Text encoding of keys and values, and byte length versus character length
  • An exact round trip, including empty strings and an empty store
  • Behavior when the saved file is missing, truncated or corrupt

Part 2 — Split the saved data into chunks of a given size

You are now also given a size. The saved data must be split into chunks no larger than that size before being written to disk. Restoring must reassemble the chunks and rebuild the same store.

Clarifying Questions for this Part Guidance

  • Does the size limit apply to each chunk file, including any header you add to it?
  • May one key-value record be split across two chunks, or must every record fit inside a single chunk? What if one record is larger than the size?
  • Should each chunk be written as a separate file?

What This Part Should Cover Guidance

  • Chunking strategy: slicing one byte stream versus packing whole records
  • Chunk naming, ordering, and the metadata needed for reassembly
  • Validation on restore: missing or damaged chunks, and leftovers from an earlier, larger save
  • Keeping the previous save intact if writing many chunks is interrupted

Part 3 — Make the existing code safe for multiple threads (discussion only)

The store is now used by several threads at once. Without writing code, discuss what can go wrong in the current implementation and how you would support concurrent get, set and shutdown calls.

What This Part Should Cover Guidance

  • Which operations race in the current code, and what the failures look like
  • Locking options (one lock, reader-writer lock, lock striping) and their trade-offs
  • A consistent snapshot for persistence without holding a lock during disk I/O
  • Behavior of calls that arrive during or after shutdown

What a Strong Answer Covers Guidance

  • A precise byte format that round-trips arbitrary keys and values, with lengths measured in bytes
  • Restore that rejects truncated or corrupt data instead of loading part of it
  • Chunking that preserves order and lets the reader detect an incomplete save
  • A concurrency design built around a consistent snapshot and a clear shutdown cut-off
  • Tests for edge cases: empty strings, separator-like characters inside data, multi-byte characters, records larger than the chunk size

Follow-up Questions Guidance

  • The store is too large to hold a second, encoded copy in memory during shutdown. How do you stream the save?
  • A crash happens halfway through shutdown . How do you make sure the previous save is not destroyed?
  • How would you make every set durable immediately, so that a crash at any moment loses nothing?
  • Most operations are reads from many threads. How does that change your locking choice, and what difference does Python's global interpreter lock make?