Implement pagination and a time-versioned key-value store
Company: Lyft
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates proficiency in data structures, algorithms, and API design for stateful systems, focusing on cursor-based pagination stability and time-versioned key-value storage with efficient time-travel reads, and is categorized under coding & algorithms with overlaps into data structures and systems design.
Part 1: Transaction Pagination with Stable Cursor
Constraints
- 0 <= len(transactions) <= 200000
- 1 <= page_size <= 1000
- Each txn_id is unique
- created_at is an integer epoch timestamp
- cursor is either None or a valid cursor previously produced by the function
Examples
Input: ([('t1', 'u1', 50, 100), ('t3', 'u1', 20, 120), ('t2', 'u1', 70, 120), ('t4', 'u2', 10, 130)], 'u1', 2, None)
Expected Output: {'items': [('t2', 'u1', 70, 120), ('t3', 'u1', 20, 120)], 'next_cursor': '120|t3'}
Explanation: Transactions for u1 are ordered by created_at descending. t2 comes before t3 because they tie on timestamp and txn_id is the tie-breaker.
Input: ([('t1', 'u1', 50, 100), ('t3', 'u1', 20, 120), ('t2', 'u1', 70, 120), ('t4', 'u2', 10, 130), ('t0', 'u1', 99, 200)], 'u1', 2, '120|t3')
Expected Output: {'items': [('t1', 'u1', 50, 100)], 'next_cursor': None}
Explanation: A newer transaction t0 was inserted before the next call, but the cursor resumes after t3, so there is no duplicate and the remaining older item is returned.
Hints
- Offset pagination is fragile when new rows appear. Think about resuming from the last seen record instead.
- Your ordering key is composite: created_at descending, then txn_id ascending. The cursor must carry enough information to resume after exactly one record.
Part 2: Time-Versioned Key-Value Store
Constraints
- 0 <= len(operations) <= 200000
- Keys and values are strings
- Timestamps are integers and are not guaranteed to be sorted
- If the same key is written at the same timestamp more than once, the most recent write wins
Examples
Input: ([('set', 'a', 'x', 5), ('set', 'a', 'y', 10), ('get', 'a', 4), ('get', 'a', 5), ('get', 'a', 7), ('get', 'a', 10), ('get', 'a', 100)],)
Expected Output: [None, 'x', 'x', 'y', 'y']
Explanation: Basic predecessor queries on one key.
Input: ([('set', 'k', 'late', 10), ('set', 'k', 'early', 3), ('set', 'k', 'replace', 10), ('get', 'k', 2), ('get', 'k', 3), ('get', 'k', 9), ('get', 'k', 10)],)
Expected Output: [None, 'early', 'early', 'replace']
Explanation: Out-of-order inserts are allowed, and the second write at timestamp 10 replaces the earlier value.
Hints
- For each key, keep versions ordered by timestamp and answer get with a predecessor/floor search.
- If duplicate timestamps are allowed, do not create two versions with the same timestamp for one key; overwrite the old value.