Level: Unknown Level
Round: Onsite · Type: Coding · Difficulty: 7/10 · Duration: 60 min · Interviewer: Neutral
Topics: Hash Table, Serialization, Data Persistence, Key-Value Store
Location: San Francisco Bay Area
Interview date: 2026-01-22
I was asked to design and implement a persistent key-value store that supports saving and restoring data using a binary middleware. The system should handle string keys and values, and serialize its state to a binary blob for durability.
The problem required designing a persistent key-value store with specific serialization and restoration requirements.
The coding question I got was:
` // This is a provided API interface. interface Medium { /** * Saves a byte array to the storage medium (e.g., file, in-memory). * @param data The byte array to persist. */ void saveBlob(byte[] data);
/**
* Retrieves the most recently saved byte array from the storage medium.
* @return The stored byte array, or null if nothing is saved.
*/
byte[] getBlob();
}
Implement the KVStore class:
KVStore(Medium medium) Initializes a new, empty key-value store using the provided storage medium. The store only interacts with data via this medium.
void put(String key, String value) Stores the specified value associated with the given key. If the key already exists, its value is updated.
String get(String key) Returns the value associated with key. If the key is not present, it returns the empty string "".
void shutdown() Serializes the current store into a custom binary format and writes it to the associated medium. After shutdown, the instance becomes closed, any subsequent get or put must fail and throw an error.
void restore() Loads the key-value data from the binary blob saved in the medium, completely replacing the in-memory store. If the blob is missing or empty, the store should become empty. `
My approach:
put and get methods.shutdown and the corresponding deserialization in restore.IOExceptions during serialization and deserialization.My Code ` import struct from abc import ABC, abstractmethod from io import BytesIO
class Medium(ABC): @abstractmethod def saveBlob(self, data): pass
@abstractmethod
def getBlob(self):
pass
class InMemoryMedium(Medium): def init(self): self.storage = None
def saveBlob(self, data):
self.storage = data
def getBlob(self):
return self.storage
class KVStore: def init(self, medium): self.map = {} self.medium = medium self.isClosed = False
def put(self, key, value):
if self.isClosed:
raise RuntimeError("Store is closed after shutdown")
self.map[key] = value
def get(self, key):
if self.isClosed:
raise RuntimeError("Store is closed after shutdown")
value = self.map.get(key)
return value if value is not None else ""
def shutdown(self):
try:
baos = BytesIO()
# Serialization Format:[num_entries: int] -> [key1_len: int][key1: byte[]][val1_len: int][val1: byte[]]
baos.write(struct.pack('>i', len(self.map)))
for key, value in self.map.items():
keyBytes = key.encode('utf-8')
valueBytes = value.encode('utf-8')
baos.write(struct.pack('>i', len(keyBytes)))
baos.write(keyBytes)
baos.write(struct.pack('>i', len(valueBytes)))
baos.write(valueBytes)
self.medium.saveBlob(baos.getvalue())
self.isClosed = True
except Exception as e:
raise RuntimeError("Serialization failed") from e
def restore(self):
data = self.medium.getBlob()
if data is None or len(data) == 0:
return
try:
bais = BytesIO(data)
self.map.clear()
entryCount = struct.unpack('>i', bais.read(4))[0]
for i in range(entryCount):
keyLength = struct.unpack('>i', bais.read(4))[0]
keyBytes = bais.read(keyLength)
key = keyBytes.decode('utf-8')
valueLength = struct.unpack('>i', bais.read(4))[0]
valueBytes = bais.read(valueLength)
value = valueBytes.decode('utf-8')
self.map[key] = value
except Exception as e:
raise RuntimeError("Deserialization failed") from e
@staticmethod
def main(args):
KVStore.test1()
KVStore.test2()
KVStore.test3()
@staticmethod
def test1():
print("===== Test 1 =====")
medium = InMemoryMedium()
store1 = KVStore(medium)
store1.put("key1", "value1")
store1.put("key2", "value2_is_longer")
store1.put("key3", "value3")
print(store1.get("key1")) # Expected: "value1"
print(store1.get("key4")) # Expected: ""
store1.shutdown()
store2 = KVStore(medium)
print(store2.get("key1")) # Expected: ""
store2.put("key4", "value4")
store2.restore()
print(store2.get("key1")) # Expected: "value1"
print(store2.get("key2")) # Expected: "value2_is_longer"
print(store2.get("key3")) # Expected: "value3"
print(store2.get("key4")) # Expected: ""
@staticmethod
def test2():
print("\n===== Test 2 =====")
medium = InMemoryMedium()
store1 = KVStore(medium)
store1.put("counter", "1")
store1.put("status", "active")
print(store1.get("counter")) # Expected: "1"
print(store1.get("status")) # Expected: "active"
store1.shutdown()
store2 = KVStore(medium)
store2.restore()
store2.put("counter", "2")
store2.put("status", "inactive")
store2.put("new_field", "added")
print(store2.get("counter")) # Expected: "2"
print(store2.get("status")) # Expected: "inactive"
print(store2.get("new_field")) # Expected: "added"
store2.shutdown()
store3 = KVStore(medium)
store3.restore()
print(store3.get("counter")) # Expected: "2"
print(store3.get("status")) # Expected: "inactive"
print(store3.get("new_field")) # Expected: "added"
@staticmethod
def test3():
print("\n===== Test 3 =====")
medium = InMemoryMedium()
store1 = KVStore(medium)
store1.put("emoji_key_😊", "emoji_value_🎉")
store1.put("unicode_key_中文", "unicode_value_日本語")
store1.put("empty_value", "")
print(store1.get("emoji_key_😊")) # Expected: "emoji_value_🎉"
print(store1.get("empty_value")) # Expected: ""
store1.shutdown()
store2 = KVStore(medium)
store2.restore()
print(store2.get("emoji_key_😊")) # Expected: "emoji_value_🎉"
print(store2.get("unicode_key_中文")) # Expected: "unicode_value_日本語"
print(store2.get("empty_value")) # Expected: ""
print(store2.get("nonexistent_key")) # Expected: ""
if name == "main": KVStore.main([]) `
Key insights:
restore correctly replaces the existing in-memory state.