Design a time-travel key-field store with TTL
Company: Tradedesk
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Take-home Project
Quick Answer: This question evaluates a candidate's understanding of time-versioned key-field storage, per-write TTL expirations, timestamp-based visibility, and atomic compare-and-set/delete semantics, exercising competency in maintaining historical state and correct value visibility over time.
Part 1: Basic Key-Field Store Operations
Constraints
- 1 <= len(operations) <= 200000
- Timestamps are non-decreasing integers encoded as strings.
- Keys and fields are non-empty lowercase strings of length at most 30.
- Values fit in a signed 32-bit integer.
- A compare operation against a missing field must fail.
Examples
Input: ([['SET', '1', 'user1', 'age', '30'], ['GET', '2', 'user1', 'age'], ['CAS', '3', 'user1', 'age', '30', '31'], ['GET', '4', 'user1', 'age'], ['CAD', '5', 'user1', 'age', '30'], ['CAD', '6', 'user1', 'age', '31'], ['GET', '7', 'user1', 'age']],)
Expected Output: ['30', 'true', '31', 'false', 'true', '']
Explanation: The value is set to 30, updated to 31 by a successful CAS, then deleted by a successful CAD.
Input: ([['GET', '1', 'k', 'f'], ['CAS', '2', 'k', 'f', '0', '1'], ['CAD', '3', 'k', 'f', '0']],)
Expected Output: ['', 'false', 'false']
Explanation: Missing fields read as empty output and cannot satisfy compare operations.
Hints
- A nested dictionary key -> field -> value is enough for this part.
- Implement one helper-style lookup behavior and reuse it for GET, CAS, and CAD.
Part 2: Key-Field Store with Sorted Scans
Constraints
- 1 <= len(operations) <= 200000
- Timestamps are non-decreasing integers encoded as strings.
- Keys, fields, and prefixes contain lowercase letters; fields do not contain commas or parentheses.
- Values fit in a signed 32-bit integer.
- Scan output must be sorted by field name ascending.
Examples
Input: ([['SET', '1', 'profile', 'name', '5'], ['SET', '2', 'profile', 'age', '30'], ['SET', '3', 'profile', 'zip', '90210'], ['SCAN', '4', 'profile'], ['SCAN_PREFIX', '5', 'profile', 'z'], ['SCAN_PREFIX', '6', 'profile', 'na']],)
Expected Output: ['age(30),name(5),zip(90210)', 'zip(90210)', 'name(5)']
Explanation: SCAN sorts fields alphabetically. Prefix scans include only matching field names.
Input: ([['SCAN', '1', 'missing'], ['SCAN_PREFIX', '2', 'missing', 'a'], ['GET', '3', 'missing', 'f']],)
Expected Output: ['', '', '']
Explanation: Scanning or reading a missing key returns an empty output string.
Hints
- A scan is just a filtered view of the current dictionary for one key.
- Sort field names before formatting; test the prefix against the field name, not against the final formatted string.
Part 3: Key-Field Store with TTL Expiration
Constraints
- 1 <= len(operations) <= 200000
- Timestamps are non-decreasing integers encoded as strings.
- 1 <= ttl <= 1000000000 for TTL writes.
- Keys, fields, and prefixes contain lowercase letters; fields do not contain commas or parentheses.
- Values fit in a signed 32-bit integer.
Examples
Input: ([['SET_TTL', '1', 'k', 'f', '10', '5'], ['GET', '1', 'k', 'f'], ['GET', '5', 'k', 'f'], ['GET', '6', 'k', 'f']],)
Expected Output: ['10', '10', '']
Explanation: The value is visible at timestamps 1 through 5, but not at timestamp 6.
Input: ([['SET_TTL', '1', 'k', 'f', '1', '3'], ['GET', '3', 'k', 'f'], ['SET', '4', 'k', 'f', '2'], ['GET', '100', 'k', 'f']],)
Expected Output: ['1', '2']
Explanation: The TTL value expires at timestamp 4, then a permanent SET overwrites it.
Hints
- Store an expiration time together with each current value. Permanent writes can use a very large expiration time.
- The interval is half-open: a value with write time t and ttl is already expired at timestamp t + ttl.
Part 4: Time-Travel Key-Field Store with TTL
Constraints
- 1 <= len(operations) <= 200000
- Timestamps are non-decreasing integers encoded as strings.
- For GET_WHEN, at_timestamp <= timestamp.
- 1 <= ttl <= 1000000000 for TTL writes.
- If multiple updates to the same field have the same timestamp, later commands in the input are considered later for subsequent queries at that timestamp.
- Keys, fields, and prefixes contain lowercase letters; fields do not contain commas or parentheses.
Examples
Input: ([['SET', '1', 'k', 'f', '10'], ['SET', '3', 'k', 'f', '20'], ['GET_WHEN', '4', 'k', 'f', '2'], ['GET_WHEN', '4', 'k', 'f', '3'], ['GET', '4', 'k', 'f'], ['CAD', '5', 'k', 'f', '20'], ['GET_WHEN', '6', 'k', 'f', '4'], ['GET_WHEN', '6', 'k', 'f', '5'], ['GET', '6', 'k', 'f']],)
Expected Output: ['10', '20', '20', 'true', '20', '', '']
Explanation: Historical queries before the deletion still see 20; queries at or after the deletion see missing.
Input: ([['SET_TTL', '10', 'k', 'f', '7', '5'], ['GET_WHEN', '12', 'k', 'f', '10'], ['GET_WHEN', '16', 'k', 'f', '14'], ['GET_WHEN', '16', 'k', 'f', '15'], ['GET', '16', 'k', 'f'], ['SET', '20', 'k', 'f', '9'], ['GET_WHEN', '21', 'k', 'f', '16'], ['GET_WHEN', '21', 'k', 'f', '20']],)
Expected Output: ['7', '7', '', '', '', '9']
Explanation: The TTL write is visible for times 10 through 14 only. A later permanent write does not change history at timestamp 16.
Hints
- Keep an append-only version list for each key-field pair. Include tombstone versions for successful deletes.
- For a historical read, binary search for the last version with write_timestamp <= at_timestamp, then check whether it was a deletion or expired by at_timestamp.