Build a Versioned In-Memory Database
Company: Anthropic
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Onsite
# Build a Versioned In-Memory Database
The source reports a four-stage in-memory database exercise but does not provide its exact method signatures. The following driver, return values, and error rules are a deterministic practice contract.
Implement `VersionedDatabase` with these methods:
```python
class VersionedDatabase:
def set(self, timestamp: int, key: str, value: str, ttl: int | None = None) -> bool: ...
def get(self, timestamp: int, key: str) -> str | None: ...
def delete(self, timestamp: int, key: str) -> bool: ...
def scan_prefix(self, timestamp: int, prefix: str) -> list[list[str]]: ...
def get_at(self, timestamp: int, key: str, as_of_timestamp: int) -> str | None: ...
```
Also implement the literal-input console driver:
```python
def run_versioned_database(operations: list[list[object]]) -> list[object]:
...
```
Process operations in list order and return one result per operation:
- `["SET", timestamp, key, value]` calls `set(..., ttl=None)` and returns `True`.
- `["SET", timestamp, key, value, ttl]` creates a version visible on `[timestamp, timestamp + ttl)` and returns `True`.
- `["GET", timestamp, key]` returns the visible value or `None`.
- `["DELETE", timestamp, key]` returns `True` and writes a tombstone only when the key is visible at that point. It returns `False` and writes nothing when the key is absent or expired.
- `["SCAN_PREFIX", timestamp, prefix]` returns `[[key, value], ...]` for all visible keys beginning with `prefix`, ordered by key ascending. An empty prefix matches every visible key.
- `["GET_AT", timestamp, key, as_of_timestamp]` returns the value visible at `as_of_timestamp`, or `None` if the key had no live version then.
Every write, overwrite, and successful delete is an immutable version. A later version supersedes an earlier one for current reads but does not erase history. A non-expiring write stays live until superseded. Expiration does not delete history. Historical lookup chooses the most recent already-processed version whose `(timestamp, operation order)` is at or before the requested time and then applies that version's TTL and tombstone state.
## Deterministic Ordering and Errors
- Operation timestamps and `as_of_timestamp` values are nonnegative integers. Operation timestamps are nondecreasing; operations sharing a timestamp take effect in list order.
- A query observes only operations processed before that query. Because operation timestamps never decrease, a later query for an earlier timestamp sees every operation at that earlier timestamp that has already appeared in the list.
- `as_of_timestamp` must not exceed the query's `timestamp`.
- Keys and values are strings; keys and values may be empty. TTL, when present, is a positive integer.
- A malformed operation, unknown command, decreasing timestamp, invalid TTL, or future `as_of_timestamp` raises `ValueError`. Validate such an operation before changing state. Ordinary misses use the `None` or `False` results above and are not exceptions.
## Performance Expectations
Discuss the cost of point reads and writes, prefix scans, and historical reads. Historical lookup should use per-key ordered history rather than replaying the full database log. Test exact expiration boundaries, overwrites at the same timestamp, expired deletes, delete-then-recreate, empty prefixes, and queries before the first version.
Quick Answer: Implement a versioned in-memory database with TTL-aware writes, tombstones, prefix scans, and historical reads. The exercise emphasizes deterministic same-timestamp ordering, atomic validation, indexed per-key history, and clear performance trade-offs.
Process a sequence of operations against a versioned in-memory key-value database and return **one result per operation**, in order.
Each operation is a list whose first element is the command name and whose second element is an integer `timestamp`. Timestamps are **nondecreasing** across the sequence.
- `["SET", t, key, value]` or `["SET", t, key, value, ttl]` — store a new immutable version of `key`. With `ttl` (a positive integer), the version is valid for the half-open interval `[t, t + ttl)` — it expires **exactly at** `t + ttl`. Result: `True`.
- `["GET", t, key]` — the value of `key` visible at time `t`, or `None` if the key has no visible value (never set, deleted, or expired).
- `["DELETE", t, key]` — if `key` has a visible value at `t`, record a tombstone (reads at time `>= t` see nothing) and return `True`; otherwise return `False`. Earlier versions remain readable through `GET_AT`.
- `["SCAN_PREFIX", t, prefix]` — every `[key, value]` pair whose key starts with `prefix` and is visible at `t`, sorted by key ascending.
- `["GET_AT", t, key, as_of]` — **historical read**: the value that was visible at time `as_of` (`0 <= as_of <= t`), considering only versions written at timestamps `<= as_of`. Tombstones and TTL expiry are evaluated as of `as_of`. Returns `None` if nothing was visible then.
**Visibility rule** (shared by `GET`, `SCAN_PREFIX`, and `GET_AT`): the most recently written version with version timestamp `<=` the read time wins; the result is `None` when that version is a tombstone or the read time is `>=` its expiry.
Constraints
- Operation timestamps are nonnegative integers in nondecreasing order; equal timestamps execute in list order.
- SET accepts string keys and values and an optional positive integer TTL.
- GET_AT may not request a time later than its query timestamp.
- SCAN_PREFIX returns visible key-value pairs sorted by key.
- Malformed operations raise ValueError before changing state.
Examples
Input: ([["SET", 1, "a", "x"], ["GET", 1, "a"]],)
Expected Output: [True, "x"]
Explanation: A value is visible immediately at the write timestamp.
Input: ([["SET", 2, "a", "x", 3], ["GET", 4, "a"], ["GET", 5, "a"]],)
Expected Output: [True, "x", None]
Explanation: TTL validity is half-open, so expiration occurs exactly at timestamp 5.
Hints
- Store an ordered version history per key instead of replaying the entire log.
- A historical lookup can scan backward from the newest version at or before the requested timestamp.
- Treat expiration as half-open and tombstones as ordinary immutable versions.