Implement an Expiring Record Store
Company: Meta
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates skills in designing and implementing an in-memory, level-based key-field-value store with lexicographic scanning, time-based versioning, TTL semantics, and efficient mutation operations.
Part 1: Basic In-Memory Record Store
Constraints
- 1 <= len(operations) <= 200000
- Each `key`, `field`, and `value` is a string of length between 1 and 100
- The total length of all strings in the input is at most 200000
- All operation names are valid and appear exactly as specified
Examples
Input: [['set', 'user', 'name', 'alice'], ['get', 'user', 'name'], ['set', 'user', 'name', 'bob'], ['get', 'user', 'name'], ['delete', 'user', 'name'], ['get', 'user', 'name']]
Expected Output: [None, 'alice', None, 'bob', True, None]
Explanation: Setting the same field again overwrites the old value.
Input: [['get', 'missing', 'field'], ['delete', 'missing', 'field'], ['set', 'a', 'x', '1'], ['delete', 'a', 'y'], ['get', 'a', 'x']]
Expected Output: [None, False, None, False, '1']
Explanation: Reading or deleting a missing field should safely return `None` or `False`.
Input: [['set', 'r', 'f1', 'v1'], ['set', 'r', 'f2', 'v2'], ['delete', 'r', 'f1'], ['get', 'r', 'f2'], ['delete', 'r', 'f2'], ['get', 'r', 'f2']]
Expected Output: [None, None, True, 'v2', True, None]
Explanation: Deleting the last remaining field removes the record, so later reads return `None`.
Input: [['set', 'k1', 'a', '1'], ['set', 'k2', 'a', '2'], ['get', 'k1', 'a'], ['get', 'k2', 'a'], ['delete', 'k1', 'a'], ['get', 'k2', 'a']]
Expected Output: [None, None, '1', '2', True, '2']
Explanation: Different keys must be stored independently.
Hints
- A dictionary from `key` to another dictionary of `field -> value` is enough for all Level 1 operations.
- After deleting a field, check whether its record became empty and remove that record too.
Part 2: Record Store with Scanning
Constraints
- 1 <= len(operations) <= 200000
- Each `key`, `field`, `value`, and `prefix` is a string of length between 0 and 100
- The total length of all strings in the input is at most 200000
- Fields in scans must be sorted lexicographically by field name
Examples
Input: [['set', 'user', 'name', 'alice'], ['set', 'user', 'age', '30'], ['set', 'user', 'email', 'a@example.com'], ['scan', 'user'], ['scan_by_prefix', 'user', 'e']]
Expected Output: [None, None, None, ['age(30)', 'email(a@example.com)', 'name(alice)'], ['email(a@example.com)']]
Explanation: Scan results must be sorted by field name, not by insertion order.
Input: [['set', 'k', 'apple', '1'], ['set', 'k', 'app', '2'], ['set', 'k', 'banana', '3'], ['scan_by_prefix', 'k', 'app'], ['delete', 'k', 'app'], ['scan_by_prefix', 'k', 'app']]
Expected Output: [None, None, None, ['app(2)', 'apple(1)'], True, ['apple(1)']]
Explanation: Only fields starting with the prefix are returned, still in lexicographic order.
Input: [['scan', 'missing'], ['scan_by_prefix', 'missing', 'a'], ['set', 'm', 'z', '9'], ['scan_by_prefix', 'm', 'a']]
Expected Output: [[], [], None, []]
Explanation: Scanning a missing record or with a prefix that matches nothing should return an empty list.
Input: [['set', 'r', 'x', '1'], ['delete', 'r', 'x'], ['scan', 'r'], ['get', 'r', 'x']]
Expected Output: [None, True, [], None]
Explanation: After the only field is deleted, the record is empty and scans should return `[]`.
Hints
- Keep the same nested dictionary structure from Part 1, then build scan results from the inner dictionary.
- For prefix scans, filter field names first, then sort the matching fields before formatting them.
Part 3: Expiring Record Store with Timestamps and TTL
Constraints
- 1 <= len(operations) <= 200000
- The sequence of timestamps across all operations is non-decreasing
- 1 <= ttl <= 1000000000
- Each `key`, `field`, `value`, and `prefix` is a string of length between 0 and 100
Examples
Input: [['set_at_with_ttl', 'user', 'token', 'abc', '10', '5'], ['get_at', 'user', 'token', '10'], ['get_at', 'user', 'token', '14'], ['get_at', 'user', 'token', '15'], ['scan_at', 'user', '15']]
Expected Output: [None, 'abc', 'abc', None, []]
Explanation: A TTL write at time 10 with TTL 5 is visible at times 10 through 14, but not at 15.
Input: [['set_at_with_ttl', 'k', 'a', '1', '1', '10'], ['set_at', 'k', 'a', '2', '5'], ['get_at', 'k', 'a', '6'], ['get_at', 'k', 'a', '20'], ['scan_at', 'k', '20']]
Expected Output: [None, None, '2', '2', ['a(2)']]
Explanation: A later write replaces both the old value and the old expiration policy.
Input: [['set_at_with_ttl', 'k', 'a', '1', '2', '3'], ['delete_at', 'k', 'a', '5'], ['set_at', 'k', 'a', '2', '6'], ['delete_at', 'k', 'a', '7'], ['get_at', 'k', 'a', '8']]
Expected Output: [None, False, None, True, None]
Explanation: The first delete happens exactly at the expiration boundary, so the field is already expired and cannot be deleted.
Input: [['scan_at', 'missing', '100'], ['set_at', 'user', 'apple', 'red', '1'], ['set_at_with_ttl', 'user', 'app', 'green', '2', '3'], ['set_at', 'user', 'banana', 'yellow', '3'], ['scan_by_prefix_at', 'user', 'app', '4'], ['scan_by_prefix_at', 'user', 'app', '5']]
Expected Output: [[], None, None, None, ['app(green)', 'apple(red)'], ['apple(red)']]
Explanation: The field `app` expires before time 5, while `apple` remains visible.
Hints
- Because timestamps never go backward, you do not need full history for each field; keeping only the current value and its expiration time is enough.
- On any read, scan, or delete at time `t`, treat a field with `expire_at <= t` as missing, and you can lazily remove it from the store.