Coding questions · Perplexity
Implement time-versioned KV store with restore
This question evaluates a candidate's ability to design and implement a time-versioned key–value store, assessing knowledge of temporal data structures, versioning semantics, time-travel and range queries, deletion semantics, and rollback/restoration.
Design and implement an in-memory key–value store where every operation is associated with a timestamp (monotonically increasing integer). The store must support time-travel queries and rollback.
Implement the following operations (you may choose function signatures, but the semantics must match):
set(key, value, ts): Setkeytovalueeffective at timets.get(key, ts): Return the value ofkeyat timets(the most recentsetat or beforetsthat hasn’t been deleted by timets). Returnnull/Noneif the key does not exist atts.delete(key, ts): Deletekeyeffective at timets.- Range query requirement: support querying a time interval for a key, e.g.
getRange(key, startTs, endTs), returning the value history within[startTs, endTs](for example, a list of segments or (timestamp, value) change points within the interval). restore(ts): Restore the entire store to exactly the state it had at timets(after all operations with timestamp<= ts), discarding the effects of any operations with timestamp> ts.
Discuss and/or implement data structures that make these operations efficient. Clarify any assumptions (e.g., whether multiple operations may share the same timestamp, and how ties are resolved).
Implement Time-Versioned KV Store with Restore
You are given a sequence of operations for an in-memory time-versioned key-value store. Implement a function solution(operations) that processes the operations in order and returns the answers for every get and getRange query.
Operations are represented as tuples:
("set", key, value, ts): setkeytovalueeffective at timestampts.("get", key, ts): return the value visible forkeyat timets, orNoneif the key does not exist at that time.("delete", key, ts): deletekeyeffective at timestampts.("getRange", key, startTs, endTs): return the visible value history forkeyinside[startTs, endTs]as a list of[timestamp, value]change points.("restore", ts): restore the entire store to exactly the state it had after all mutations with timestamp<= ts, permanently discarding every mutation with timestamp> ts.
Important clarifications:
- Process operations in input order.
- Only
setanddeletecreate historical changes. getandgetRangeare queries only; they do not change history.restoretruncates the current timeline; discarded future mutations do not come back later.- Mutation timestamps (
set/delete) are non-decreasing in input order. Equal timestamps are allowed. - If multiple mutations share the same timestamp, input order breaks ties: the later mutation is the visible one at that timestamp.
- For
getRange, return visible change points, not every raw mutation. If the key already exists atstartTs, include[startTs, value_at_startTs]as the first entry. Then include later timestamps in(startTs, endTs]where the visible value changes. Deletions are represented byNone.
Your goal is to support these operations efficiently.
Constraints
- 0 <= len(operations) <= 2 * 10^5
- 0 <= ts, startTs, endTs <= 10^9
- Timestamps of
setanddeleteoperations are non-decreasing in input order - Equal mutation timestamps are allowed; later input order wins for that timestamp
- Keys are strings; values can be any Python value comparable with
==
Examples
Example 1
Input: [("set", "a", "x", 1), ("set", "a", "y", 4), ("get", "a", 3), ("getRange", "a", 1, 5), ("delete", "a", 6), ("get", "a", 6)]
Output: ["x", [[1, "x"], [4, "y"]], None]
Explanation: At time 3, a is x. The range [1, 5] shows a starting as x and changing to y at time 4. After deleting at time 6, get(a, 6) returns None.
Example 2
Input: [("set", "a", 10, 1), ("set", "b", 20, 2), ("set", "a", 30, 5), ("get", "a", 5), ("restore", 2), ("get", "a", 5), ("get", "b", 100), ("getRange", "a", 1, 10)]
Output: [30, 10, 20, [[1, 10]]]
Explanation: Before restore, a is 30 at time 5. Restoring to time 2 discards the change at time 5, so later queries see a = 10 and b = 20. The only visible history for a is its value from time 1.
Hints
- Store, for each key, a sorted history of its mutations. Because mutation timestamps are non-decreasing, you can append instead of inserting.
- To make
restore(ts)efficient, keep a global stack of applied mutations and pop mutations from the end while their timestamp is greater thants.