Design SnapshotMap with Sparse Histories and Constant-Time Snapshots
SnorkelSoftware Engineer · Technical ScreenLast updated
Design an in-memory key-value store supporting point-in-time snapshots, sparse version histories, binary-search historical reads, tombstones, and write coalescing.
Problem Statement
Design an in-memory key-value map, SnapshotMap, that supports constant-time snapshots and historical point-in-time queries.
To make storage scalable across long-lived processes, the data structure must maintain sparse histories: keys that are not modified in a snapshot period must not duplicate memory. In addition, consecutive modifications or deletions to the same key within the same snapshot window must be coalesced into a single historical record.
Implement the SnapshotMap class with the following methods:
__init__(): Initializes the map. The initial active snapshot ID is0.put(key: str, val: int) -> None: Sets the key tovalfor the current active snapshot. Overwrites any previous uncommitted write in the current snapshot.get(key: str) -> Optional[int]: Returns the latest value ofkey. ReturnsNoneif the key does not exist or was deleted.delete(key: str) -> None: Removeskeyin the current active snapshot by writing a tombstone. If the key already does not exist, this is a no-op.take_snapshot() -> int: Freezes the current state under the current snapshot ID and advances the active snapshot ID by 1. Returns the frozen snapshot ID.get_snapshot(key: str, snap_id: int) -> Optional[int]: Returns the value ofkeyas ofsnap_id. ReturnsNoneif the key did not exist or was marked deleted at that snapshot. RaisesValueErrorifsnap_idis invalid (i.e.,=current active snapshot ID).
Complexity Requirements
take_snapshot:O(1)time.put,delete:O(1)amortized time.get:O(1)average time.get_snapshot:O(log H_k)time, whereH_kis the number of distinct snapshot versions recorded forkey.- Space:
O(K + U), whereKis the number of distinct keys andUis the total number of unique(key, snap_id)modification pairs.
Example
sm = SnapshotMap()
sm.put("a", 10)
sm.put("b", 20)
# Coalescing test: overwrite "a" before taking snapshot 0
sm.put("a", 15)
snap0 = sm.take_snapshot() # returns 0; captures {"a": 15, "b": 20}
sm.put("a", 30)
sm.delete("b")
snap1 = sm.take_snapshot() # returns 1; captures {"a": 30, "b": None}
sm.get("a") # returns 30
sm.get("b") # returns None
sm.get_snapshot("a", snap0) # returns 15
sm.get_snapshot("b", snap0) # returns 20
sm.get_snapshot("b", snap1) # returns None (tombstone)
sm.get_snapshot("c", snap0) # returns None (never set)
Edge Cases to Consider
- Querying a
snap_idthat does not exist yet (should raise an exception or handle out-of-range cleanly). - A key deleted and recreated across different snapshots.
- Multiple
deletecalls orputfollowed bydeletein the same snapshot epoch. - Keys that were never modified after snapshot 0 must correctly read from their initial entry in future snapshots.
Hints
Hint 1
Track a global `current_snap_id` and map each key to a list of historical modifications containing `(snap_id, value_or_tombstone)`.
Hint 2
For write coalescing, inspect the last entry in a key's history list; if its recorded snapshot matches `current_snap_id`, overwrite it in place instead of appending.
Hint 3
To resolve `get_snapshot(key, snap_id)`, perform a binary search (e.g., `bisect_right`) over the key's history to locate the largest recorded snapshot ID less than or equal to `snap_id`.
Follow-up questions
- How would you implement a `compact(retention_snap_id)` method to reclaim memory from history entries older than a specified snapshot ID?
- How does the binary search approach compare to using persistent data structures (e.g., copy-on-write trees) if reads need to be `O(1)` instead of `O(log H_k)`?
- How would you ensure thread safety for concurrent writers and readers while keeping `take_snapshot()` non-blocking?
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.