Design In-Memory Transactions with Snapshots and Savepoints
NextdoorSoftware Engineer · Technical ScreenLast updated
Tests your ability to design an in-memory key-value store supporting nested transactions, named savepoints, and reliable rollback semantics using undo logs or snapshotting.
Problem Statement
Design an in-memory transactional key-value store that supports nested transactions, named savepoints, and accurate restoration of previous states (including key deletions).
Implement the TransactionalKVStore class with the following methods:
get(key: str) -> Optional[str]: Returns the value ofkey, orNoneif the key does not exist.set(key: str, value: str) -> None: Setskeytovaluein the current transactional context.delete(key: str) -> None: Deleteskeyin the current transactional context. If the key does not exist, does nothing.begin() -> None: Starts a new transaction block. Transactions can be nested.savepoint(name: str) -> None: Creates a named checkpoint within the current transaction block. If a savepoint with the same name already exists in the current transaction block, it is overwritten.rollback_to(name: str) -> bool: Rolls back all changes made since the savepointnamewithin the active transaction stack, keeping the savepoint active. ReturnsTrueif the savepoint exists, orFalseif no such savepoint is found.rollback() -> bool: Rolls back all changes in the most recent uncommitted transaction block and closes it. ReturnsTrueon success, orFalseif there is no open transaction.commit() -> bool: Persists all changes from all active transactions to the base state and closes all open transactions. ReturnsTrueon success, orFalseif there is no open transaction.
Complexity Requirements
- Operations outside transactions must take
O(1)average time. get,set, anddeleteshould takeO(1)amortized time even when multiple transactions are active.- Be explicit about the space and time trade-offs between an undo-log approach and a snapshot/layered-map approach.
Example
kv = TransactionalKVStore()
kv.set("a", "10")
kv.begin()
kv.set("a", "20")
kv.savepoint("sp1")
kv.set("b", "30")
kv.delete("a")
assert kv.get("a") is None
assert kv.get("b") == "30"
# Roll back to savepoint sp1
assert kv.rollback_to("sp1") is True
assert kv.get("a") == "20"
assert kv.get("b") is None
# Roll back outer transaction
assert kv.rollback() is True
assert kv.get("a") == "10"
assert kv.get("b") is None
Edge Cases
- Deleting a key that was created in the current transaction vs. deleting a key that existed before the transaction began.
- Calling
commit(),rollback(), orrollback_to()when no transaction is active. - Rolling back to a savepoint multiple times.
Hints
Hint 1
An undo log (recording inverse operations such as 'revert key to previous value' or 'delete newly inserted key') avoids duplicating the entire dataset on every transaction or savepoint.
Hint 2
Distinguish between keys that previously held a value and keys that did not exist before by storing a sentinel or distinct previous-state record in your log.
Hint 3
Named savepoints can be indexed as markers or snapshots of the undo log's length at the moment of creation.
Follow-up questions
- How does the time and space complexity of an undo log compare to a copy-on-write / layered dictionary approach when reads are much more frequent than writes?
- How would you extend this design to support concurrent transactions with snapshot isolation and conflict detection?
- If transaction logs needed to be persisted to disk for durability (WAL), how would you structure checkpoints and crash recovery?
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.