Katkat

Serialize and Restore an In-Memory Key-Value Store

OpenAISoftware Engineer · Technical Screen

Last updated

Design and implement a deterministic binary serializer and strict deserializer for a string key-value store, ensuring robust input validation against corruption, truncation, duplicate keys, and trailing data.

Problem Statement

You are tasked with implementing a deterministic binary serialization and deserialization protocol for an in-memory key-value store where both keys and values are arbitrary UTF-8 strings.

Your implementation must be canonical (identical key-value pairs always produce the exact same byte representation) and defensive (corrupted, ambiguous, or malicious byte streams must be safely rejected by raising a ValueError).

Binary Format Specification

  1. Entry Count: A 4-byte unsigned integer in big-endian format (>I), indicating the number of key-value pairs N.
  2. Entries: N sequential key-value entries sorted in ascending lexicographical order by raw key bytes.
    • Key Length: 4-byte unsigned big-endian integer.
    • Key Bytes: Raw UTF-8 encoded bytes.
    • Value Length: 4-byte unsigned big-endian integer.
    • Value Bytes: Raw UTF-8 encoded bytes.

Validation Rules

The deserialize method must raise a ValueError if any of the following occur:

  • Truncation / Unexpected EOF: The byte stream ends before all declared fields or entries are fully read.
  • Trailing Bytes: Extraneous bytes remain after decoding all declared entries.
  • Duplicate or Out-of-Order Keys: Keys are not strictly increasing lexicographically (this enforces uniqueness and canonical ordering).
  • Size Limits: Any key length exceeds 64 KiB (65,536 bytes) or any value length exceeds 1 MiB (1,048,576 bytes).
  • Malformed Encoding: Key or value bytes are not valid UTF-8.

Class Interface

class KVStoreSerializer:
    MAX_KEY_SIZE: int = 65_536       # 64 KiB
    MAX_VALUE_SIZE: int = 1_048_576  # 1 MiB

    def serialize(self, store: dict[str, str]) -> bytes:
        """Serializes a dictionary of string keys and values into bytes."""
        pass

    def deserialize(self, data: bytes) -> dict[str, str]:
        """Restores a dictionary from bytes. Raises ValueError if invalid."""
        pass

Examples

Example 1: Empty Store

  • Input: {}
  • Serialized: b'\x00\x00\x00\x00' (4 bytes, count = 0)
  • Deserialized: {}

Example 2: Single Entry

  • Input: {"a": "b"}
  • Serialized: b'\x00\x00\x00\x01\x00\x00\x00\x01a\x00\x00\x00\x01b'
    • Count = 1 (\x00\x00\x00\x01)
    • Key len = 1 (\x00\x00\x00\x01), Key = 'a'
    • Value len = 1 (\x00\x00\x00\x01), Value = 'b'
  • Deserialized: {"a": "b"}

Hints

Hint 1

Prefix every variable-length payload with a fixed 4-byte unsigned integer and use Python's struct module for uniform endianness.

Hint 2

Sort dictionary items by their raw UTF-8 key bytes prior to serializing, and enforce strictly increasing byte order during deserialization to reject both duplicates and unsorted inputs.

Hint 3

Track an integer read cursor through the input buffer to verify that declared lengths match available bytes and that the cursor equals len(data) at the end.

Follow-up questions

  • How would you redesign the format to support zero-copy lookups without deserializing the entire payload into memory?
  • How could this protocol be extended to support schema evolution, such as version headers or nested data types (ints, lists, nested maps)?
  • How would you handle streaming serialization and deserialization for stores with millions of entries that cannot fit in RAM?