Coding questions · xAI
Implement an in-memory database with TTL and backup
This question evaluates proficiency in designing and implementing mutable in-memory data structures, field-level CRUD and scanning, timestamped operations with TTL semantics, and backup/restore snapshot management, focusing on data structures, temporal reasoning, and state management in the Coding & Algorithms domain.
In-Memory Database (Levels 1–4: TTL and Backup/Restore)
Implement an in-memory database that stores records identified by a string key. Each record contains multiple string field → string value pairs.
You must support a set of operations that progressively add features.
Data model
keyis a string.- Each
keymaps to a set of fields. - Each
fieldmaps to avalue(both strings).
If an operation refers to a missing key or field, treat it as absent.
Assumption to make outputs well-defined (typical for OAs):
get*returns""(empty string) when absent.delete*returnstrueif something was deleted, elsefalse.scan*returns an empty list when nothing matches.
Level 1: Basic CRUD on fields
Implement:
set(key, field, value)get(key, field) -> stringdelete(key, field) -> bool
set inserts or overwrites the field’s value.
Level 2: Read-only listing
Implement:
scan(key) -> list[string]scan_by_prefix(key, prefix) -> list[string]
Return format:
- Each returned element is formatted as
"field(value)". - Results are sorted lexicographically by
field. scan_by_prefixreturns only fields whose name starts withprefix.
Level 3: Timestamped operations + TTL
Add timestamped variants of the above operations. Tests will use either timestamped APIs or non-timestamped APIs, but never mix them.
All timestamped operations accept an integer timestamp.
Implement:
set_at(key, field, value, timestamp)set_at_with_ttl(key, field, value, timestamp, ttl)get_at(key, field, timestamp) -> stringdelete_at(key, field, timestamp) -> boolscan_at(key, timestamp) -> list[string]scan_by_prefix_at(key, prefix, timestamp) -> list[string]
TTL semantics:
set_at_with_ttlmakes the field valid over the half-open interval:- valid in
[timestamp, timestamp + ttl)
- valid in
- Expired fields must not appear in
get_at,scan_at, or prefix scans. - Time always moves forward: timestamps provided to operations are non-decreasing.
Level 4: Backup and restore
Implement:
backup(timestamp)restore(timestamp, timestamp_to_restore)
Backup requirements:
backup(t)stores a snapshot of the database state at timet.- For fields with TTL, the backup must capture remaining TTL at backup time (i.e., how much lifetime is left at
t).
Restore requirements:
restore(now, timestamp_to_restore)restores the database from the latest backup whose backup time is ≤timestamp_to_restore.- After restoring at current time
now, TTL expiration must be recalculated based on remaining TTL stored in the backup:- If a field had remaining TTL
rin the backup, then after restore at timenowit should expire atnow + r.
- If a field had remaining TTL
- Fields that were already expired at the moment of backup should not be present in that backup.
Your implementation should correctly handle overwrites, deletions, scans, TTL expiry, and backup/restore interactions under the monotonic-time guarantee.
Part 1: Basic CRUD on In-Memory Database Fields
Implement a simple in-memory key-value store that supports basic CRUD operations on record fields, then return the results of all read/delete queries.
Each record is identified by a string key, and every key owns a collection of field -> value pairs, where both field and value are strings.
Function
def solution(queries):
queries is a list of operations. Each operation is itself a list whose first element is the operation name. Process the operations in order and return a list containing the output of every GET and DELETE operation, in the order they occur.
Operations
-
['SET', key, field, value]— Insert thefield -> valuepair underkey. If the key does not yet exist, create it. If the field already exists, overwrite its value. Produces no output. -
['GET', key, field]— Return the stored value forfieldunderkey. If the key does not exist, or the key exists but the field does not, return the empty string''. Appends one value to the output. -
['DELETE', key, field]— Removefieldfromkey. ReturnTrueif the field existed and was removed; otherwise returnFalse. Appends one boolean to the output.
Output
Return a list with one entry per GET and per DELETE, in query order. SET operations contribute nothing, so a run containing only SET operations returns an empty list [].
Examples
Example 1
queries = [
['SET', 'user1', 'name', 'alice'],
['GET', 'user1', 'name'],
['SET', 'user1', 'name', 'bob'],
['GET', 'user1', 'name'],
['DELETE', 'user1', 'name'],
['GET', 'user1', 'name'],
['DELETE', 'user1', 'name'],
]
Output: ['alice', 'bob', True, '', False]
GET user1.name→'alice'- After the second
SET,GET user1.name→'bob'(overwritten) DELETE user1.name→True(field existed)GET user1.name→''(field no longer exists)DELETE user1.name→False(already gone)
Example 2
queries = [['GET', 'missing', 'x'], ['DELETE', 'missing', 'x']]
Output: ['', False] — reading or deleting from a key that was never set returns '' and False respectively.
Example 3
queries = [
['SET', 'a', 'x', '1'],
['SET', 'a', 'y', '2'],
['DELETE', 'a', 'x'],
['GET', 'a', 'x'],
['GET', 'a', 'y'],
]
Output: [True, '', '2'] — deleting one field of a key leaves the key's other fields intact.
Constraints
0 <= len(queries) <= 100000- Each
key,field, andvalueis a string of length 1 to 100. - Only the operations
'SET','GET', and'DELETE'appear. - You may assume average O(1) hash-map operations.
Hints
- A nested dictionary works well: one map from key to another map of field to value.
- After deleting a field, you may remove the key entirely if it has no fields left.
Approach
The store is modeled as a nested hash map: db maps each string key to an inner dict of field -> value. This two-level structure mirrors the problem exactly — a record (key) owns many field/value pairs — and gives average O(1) access at both levels.
The code processes queries one at a time, dispatching on q[0]:
-
SET key field value—db.setdefault(key, {})[field] = value.setdefaultreturns the existing inner dict, or inserts a fresh{}and returns it if the key is new, in a single step; the field is then assigned (insert or overwrite). Produces no output. -
GET key field—db.get(key, {}).get(field, ''). The chained.getcalls make both lookups safe: a missing key yields the empty default{}, and a missing field yields''. Either way the spec's''-on-miss rule is satisfied without explicitifchecks. -
DELETE key field— first verifykey in db and field in db[key]. If present,del db[key][field]removes it; then if that inner dict became empty,del db[key]prunes the now-orphan key so empty records don't accumulate. AppendTrue. Otherwise appendFalse.
Only GET and DELETE append to out, so the returned list contains exactly those outputs in query order (test 4 confirms a SET-only run returns []).
Why it's correct: every operation touches only the targeted key/field; the get-with-default pattern makes absence indistinguishable from an empty record for reads; and pruning empty inner dicts keeps GET behavior unchanged because db.get(key, {}) already treats a missing key as empty. Hash-map semantics give the required average-constant work per query.
- Time complexity: O(Q) average, where Q is the number of queries — each query does a constant number of average-O(1) hash-map operations.
- Space complexity: O(T), where T is the number of distinct (key, field) pairs currently stored (plus O(Q) for the output list in the worst case).
Part 2: Sorted Scan and Prefix Scan in an In-Memory Database
Implement an in-memory database that stores fields per record and supports point operations plus two ordered scans.
Implement the function:
def solution(queries):
Data model
The database holds records. Each record is identified by a string key and stores a collection of field → value pairs, where both field and value are strings. Setting an existing field overwrites its value (last write wins).
Input
queries— a list of operations, processed in order. Each operation is itself a list of strings whose first element is the operation name, followed by its arguments:
| Operation | Form | Behavior | Output |
|---|---|---|---|
| SET | ['SET', key, field, value] | Create the record if needed, then set/overwrite field to value. | (none — produces no output) |
| GET | ['GET', key, field] | Look up the value of field in key. | The stored value string, or '' if the key or field does not exist. |
| DELETE | ['DELETE', key, field] | If both the key and field exist, remove that field (and remove the record entirely once it has no remaining fields). | True if a field was removed, otherwise False. |
| SCAN | ['SCAN', key] | Read all fields of key. | A list of strings 'field(value)', sorted lexicographically by field name. |
| SCAN_BY_PREFIX | ['SCAN_BY_PREFIX', key, prefix] | Same as SCAN, but only include fields whose names start with prefix. | A list of strings 'field(value)', sorted lexicographically by field name. |
Output
Return a list containing the outputs of every query except SET, in the order the queries were processed. Each non-SET query contributes exactly one entry:
GET→ a value string (''when absent)DELETE→ a boolean (True/False)SCAN/SCAN_BY_PREFIX→ a (possibly empty) list of'field(value)'strings
Scan formatting and edge cases
- Each matched field is formatted as the literal
fieldname, followed by itsvaluewrapped in parentheses:'field(value)'. - Results of
SCANandSCAN_BY_PREFIXare always sorted lexicographically by field name before formatting. - A scan on a missing key, or a
SCAN_BY_PREFIXwhere no field matches the prefix, returns an empty list[].
Example
queries = [
['SET', 'user', 'b', '2'],
['SET', 'user', 'a', '1'],
['SCAN', 'user'],
['SCAN_BY_PREFIX', 'user', 'a'],
]
Output: [['a(1)', 'b(2)'], ['a(1)']]
The two SETs produce no output. SCAN 'user' lists both fields sorted by name. SCAN_BY_PREFIX 'user' 'a' lists only the field starting with 'a'.
Constraints
0 <= len(queries) <= 100000- Each key, field, value, and prefix is a string of length
0to100. - Results for scan operations must be sorted lexicographically by field name.
- Average O(1) hash-map operations may be assumed.
Hints
- Keep the same nested dictionary structure from basic CRUD.
- For a scan, collect matching field names first, sort them, then build the 'field(value)' strings.
Approach
Approach. Model the database as a two-level hash map: db maps each record key to an inner dict of field -> value. This gives average O(1) point operations while keeping each record's fields grouped for scanning.
Per-operation logic:
SET key field value—db.setdefault(key, {})[field] = valuelazily creates the inner dict on first write, then sets (or overwrites) the field. No output is appended (SET results are excluded).GET key field—db.get(key, {}).get(field, '')safely returns the stored value, or''when the key or field is missing.DELETE key field— if both key and field exist, delete the field; if the inner dict becomes empty, drop the key entirely (so a laterSCANon it returns[]via the missing-key path) and appendTrue; otherwise appendFalse.SCAN/SCAN_BY_PREFIX— handled byformat_scan. It readsdb.get(key, {})(empty dict for missing keys → empty result), collects fields, optionally filters to those wherefield.startswith(prefix), sorts them lexicographically, then formats each as'field(value)'.
Why it's correct. Inner-dict semantics give last-write-wins for repeated SETs on the same field. The empty-record cleanup ensures missing keys and emptied keys behave identically. Sorting before formatting guarantees the required lexicographic-by-field ordering. The prefix is None branch lets one helper serve both scan variants. Only non-SET queries push to out, matching the spec.
- Time complexity: O(1) average for SET/GET/DELETE; O(F log F) per SCAN/SCAN_BY_PREFIX (F = fields under the queried key)
- Space complexity: O(T), where T is the total number of stored (key, field) pairs
Part 3: Timestamped In-Memory Database with TTL
Implement a timestamped in-memory key-value database that supports an optional time-to-live (TTL) on individual fields.
Each top-level key maps to a set of field → value pairs, where both fields and values are strings. Every operation carries an explicit timestamp, and you must answer each query as of that timestamp.
Function
Implement:
def solution(queries):
...
queries is a list of operations. Each operation is itself a list of strings whose first element is the operation name. Process the operations in the given order and return a single list containing the results of the operations that produce output (defined below), in the same order they occur.
Operations
All numeric arguments (timestamps and TTLs) are given as decimal strings and should be interpreted as integers.
| Operation | Argument layout | Returns |
|---|---|---|
SET_AT | ["SET_AT", key, field, value, timestamp] | nothing |
SET_AT_WITH_TTL | ["SET_AT_WITH_TTL", key, field, value, timestamp, ttl] | nothing |
GET_AT | ["GET_AT", key, field, timestamp] | the value string, or "" |
DELETE_AT | ["DELETE_AT", key, field, timestamp] | True or False |
SCAN_AT | ["SCAN_AT", key, timestamp] | a list of strings |
SCAN_BY_PREFIX_AT | ["SCAN_BY_PREFIX_AT", key, prefix, timestamp] | a list of strings |
Only GET_AT, DELETE_AT, SCAN_AT, and SCAN_BY_PREFIX_AT contribute to the returned list. SET_AT and SET_AT_WITH_TTL produce no output.
Semantics
-
SET_AT— Setfield = valueunderkeyattimestamp, with no expiry. If the field already exists (including one previously set with a TTL), it is overwritten and any prior TTL is removed. -
SET_AT_WITH_TTL— Setfield = valueunderkeyattimestampwith the giventtl. The field is valid during the half-open interval[timestamp, timestamp + ttl)— i.e. it exists at and aftertimestampbut is treated as absent once the current time reachestimestamp + ttl. As withSET_AT, this overwrites any existing value/TTL for that field. -
GET_AT— Return the value offieldunderkeyas oftimestamp. Return""(empty string) if the key or field does not exist or the field has expired. -
DELETE_AT— Deletefieldunderkeyas oftimestamp. ReturnTrueif a live (non-expired) field was actually present and removed; returnFalseif the key/field does not exist or the field has already expired. -
SCAN_AT— Return all live (non-expired) fields underkeyas oftimestamp, formatted as"field(value)", sorted lexicographically by field name. Return an empty list if the key does not exist or has no live fields. -
SCAN_BY_PREFIX_AT— Same asSCAN_AT, but include only fields whose name starts withprefix. Results are likewise formatted as"field(value)"and sorted lexicographically by field name.
Expiry rules
- An expired field behaves exactly as if it were absent for all subsequent gets, deletes, and scans.
- The timestamps supplied across the queries are non-decreasing, so once a field has expired it never becomes valid again.
Output
Return the list of results for the output-producing operations, in query order. For example, a GET_AT that finds a live value contributes that value string; a SCAN_AT contributes its (possibly empty) sorted list; a DELETE_AT contributes True or False.
Constraints
0 <= len(queries) <= 100000- All timestamps are decimal strings representing integers in
[0, 10^9]. - All TTL values are positive decimal strings representing integers in
[1, 10^9]. - The current timestamps in the queries are non-decreasing.
Examples
Example 1
Input: [['SET_AT_WITH_TTL', 'k', 'a', 'x', '10', '5'], ['GET_AT', 'k', 'a', '10'], ['GET_AT', 'k', 'a', '14'], ['GET_AT', 'k', 'a', '15'], ['DELETE_AT', 'k', 'a', '15']]
Output: ['x', 'x', '', False]
Explanation: The field is valid at times 10 through 14, but not at 15 because the TTL interval is [10, 15).
Example 2
Input: [['SET_AT', 'u', 'name', 'Ann', '1'], ['SET_AT_WITH_TTL', 'u', 'age', '20', '2', '3'], ['SCAN_AT', 'u', '3'], ['SET_AT', 'u', 'age', '21', '4'], ['GET_AT', 'u', 'age', '6'], ['SCAN_BY_PREFIX_AT', 'u', 'a', '6']]
Output: [['age(20)', 'name(Ann)'], '21', ['age(21)']]
Explanation: The TTL version of 'age' is alive at time 3. It is overwritten at time 4 by a non-TTL value, so it still exists at time 6.
Hints
- Store an absolute expiration time like expire_at = timestamp + ttl instead of storing ttl directly.
- Because time never goes backward, you can lazily remove expired fields whenever a key is touched.
Approach
Data model. The database is a nested dict db: key -> {field -> [value, expire_at]}. Each field stores its value plus an absolute expiry timestamp (None means no TTL). TTL semantics are the half-open interval [t, t+d), so SET_AT_WITH_TTL records expire_at = t + ttl, and a field is expired exactly when the current time satisfies now >= expire_at.
Lazy expiry. Rather than scheduling deletions, the code expires fields lazily. Every operation first calls purge_key(key, t), which scans that one key's fields, deletes any with expire_at is not None and t >= expire_at, and drops the key entirely if it becomes empty. Because timestamps are non-decreasing, once a field is past its expiry it stays expired, so purging on touch is sufficient — expired fields are guaranteed absent for all subsequent gets, deletes, and scans.
Operations.
SET_AT/SET_AT_WITH_TTL: purge, thensetdefault(key, {})[field] = [value, expire]. A plainSET_AToverwrites any prior TTL withNone.GET_AT: purge, then returnrecord[field][0]or''if absent/expired.DELETE_AT: purge, then delete the field if present (returningTrue), elseFalse; the key is removed if it empties.SCAN_AT/SCAN_BY_PREFIX_AT: purge, thenscan_listcollects live fields (optionally filtered byprefixviastartswith), sorts them lexicographically, and formats each asfield(value).
Results for the four reading ops are appended to out in query order. Correctness rests on always purging before reading and on the absolute-expiry comparison matching the [t, t+d) rule.
- Time complexity: Per point op (SET/GET/DELETE): O(F_k) for the lazy purge of the touched key's F_k fields, then O(1) work. A scan is O(F_k log F_k) due to sorting the live fields. Over n queries the total is O(sum of purge+scan costs); each scan dominated by its sort.
- Space complexity: O(T), where T is the number of currently-live stored fields across all keys (each field holds a [value, expire_at] pair). Scan output adds O(F_k) transient space for the queried key.
Part 4: Backup and Restore in a Timestamped Database with TTL
Implement a timestamped, in-memory key–value database that supports per-field TTL (time-to-live) plus backup and restore operations.
The database is a two-level store: each key maps to a set of fields, and each field holds a string value. A field may optionally have a TTL, after which it expires and is treated as if it never existed. All operations carry an explicit timestamp, so behavior is fully deterministic.
Function
def solution(queries):
...
queriesis a list of operations. Each operation is a list of strings: the operation name followed by its arguments.- Return a list containing the result of every operation that produces output (
GET_AT,DELETE_AT,SCAN_AT,SCAN_BY_PREFIX_AT), in the order those operations appear.SET_AT,SET_AT_WITH_TTL,BACKUP, andRESTOREproduce no output.
All numeric arguments (timestamps, TTLs, restore arguments) arrive as decimal strings and should be parsed as integers.
Operations
["SET_AT", key, field, value, timestamp]
Set field of key to value with no expiry. Overwrites any existing value for that field. No output.
["SET_AT_WITH_TTL", key, field, value, timestamp, ttl]
Set field of key to value with a TTL of ttl, so the field expires at timestamp + ttl. Overwrites any existing value. No output.
["GET_AT", key, field, timestamp]
Return the current value of field under key as a string. If the field does not exist or has expired at timestamp, return the empty string "".
["DELETE_AT", key, field, timestamp]
Delete field from key. Return True if a live (non-expired) field was present and removed at timestamp; otherwise return False.
["SCAN_AT", key, timestamp]
Return a list of strings for all live fields under key, sorted ascending by field name, each formatted as "field(value)". If the key has no live fields, return an empty list.
["SCAN_BY_PREFIX_AT", key, prefix, timestamp]
Same as SCAN_AT, but only include fields whose name starts with prefix. Results are sorted ascending by field name and formatted as "field(value)".
["BACKUP", timestamp]
Take a snapshot of the entire live database at timestamp. No output.
["RESTORE", now, timestamp_to_restore]
Replace the entire database with a previously saved snapshot. No output.
Expiry rule
A field with expiry time E is live while current_time < E and expired once current_time >= E. Expired fields must never be returned by GET_AT, counted by DELETE_AT, or listed by either scan operation, and must never be included in a backup.
Backup semantics
- A
BACKUPat timetsnapshots only the fields that are live att; already-expired fields are excluded. - For each TTL field, the snapshot stores the field's remaining TTL (
E - t), not its absolute expiry time. Fields without a TTL are stored as non-expiring.
Restore semantics
RESTORE is given two timestamps: the current time now, and a timestamp_to_restore (which may point to any past time).
- Choose the latest backup whose backup time is
<= timestamp_to_restore. - Restore that snapshot as the live database, treating
nowas the moment of restoration. Each stored field's remaining TTLris converted back into an absolute expiry ofnow + r, so the field expiresrunits after the restore. Fields without a TTL remain non-expiring. - If no backup has a time
<= timestamp_to_restore, restore to an empty database.
Ordering guarantees
The current-time argument used by SET_AT, SET_AT_WITH_TTL, GET_AT, DELETE_AT, SCAN_AT, SCAN_BY_PREFIX_AT, BACKUP, and the now argument of RESTORE are non-decreasing across the query sequence. Only timestamp_to_restore may refer to an arbitrary past time.
Constraints
0 <= len(queries) <= 100000- All timestamps, TTL values, and restore arguments are decimal strings representing integers in
[0, 10^9]. - All TTL values are positive.
Examples
Example 1
Input: [['SET_AT_WITH_TTL', 'doc', 'a', '1', '10', '5'], ['BACKUP', '12'], ['GET_AT', 'doc', 'a', '13'], ['RESTORE', '20', '12'], ['SCAN_AT', 'doc', '22'], ['GET_AT', 'doc', 'a', '23']]
Output: ['1', ['a(1)'], '']
Explanation: At backup time 12, the field has 3 units of TTL left. After restoring at time 20, it is alive at 22 and expires at 23.
Example 2
Input: [['SET_AT', 'k', 'x', 'A', '1'], ['BACKUP', '2'], ['SET_AT', 'k', 'x', 'B', '3'], ['BACKUP', '4'], ['DELETE_AT', 'k', 'x', '5'], ['RESTORE', '6', '3'], ['GET_AT', 'k', 'x', '6'], ['RESTORE', '7', '100'], ['GET_AT', 'k', 'x', '7']]
Output: [True, 'A', 'B']
Explanation: Restoring to time 3 uses the backup from time 2, so the value is 'A'. Restoring to time 100 uses the later backup from time 4, so the value is 'B'.
Hints
- At backup time t, store remaining_ttl = expire_at - t for live TTL fields.
- Since backups are taken in time order, you can keep them in a list and use binary search to find the latest backup with time <= timestamp_to_restore.
Approach
The database is a nested dict db[key][field] = [value, expire_at], where expire_at is None for non-TTL fields or the absolute expiry time t + ttl. TTL is handled lazily: purge_key(key, t) is called before every operation on a key and drops any field whose expire_at <= t, deleting the key entirely if it becomes empty. This keeps writes O(1) while guaranteeing reads never see stale data.
Point ops (SET_AT, SET_AT_WITH_TTL, GET_AT, DELETE_AT) purge the touched key, then mutate/read the dict. Scans (SCAN_AT, SCAN_BY_PREFIX_AT) purge, filter fields by optional prefix, sort them, and format as field(value).
BACKUP calls build_snapshot(t), which walks the whole db once. For each live field (skipping any already-expired one, so expired fields never enter a backup) it stores the remaining TTL expire_at - t in the snapshot, and rebuilds live_db with absolute expiries preserved. The snapshot and its time t are appended to parallel lists backups / backup_times (times are non-decreasing, so backup_times stays sorted).
RESTORE(now, target) uses bisect_right(backup_times, target) - 1 to find the latest backup with time <= target. If none exists, the db is reset to empty. Otherwise it rehydrates each stored field, converting remaining TTL r back to an absolute expiry now + r (and leaving None as-is). This is correct because storing remaining rather than absolute TTL is exactly what lets the same field expire r units after the restore time, regardless of when the restore happens.
- Time complexity: Point ops are O(1) amortized plus lazy purge of the touched key; each scan is O(F log F) for F fields under the key; BACKUP is O(N) over all live fields; RESTORE is O(log K + L) for K backups and L fields in the chosen snapshot.
- Space complexity: O(N + S), where N is the current number of live fields and S is the total number of fields stored across all retained backups.