Implement timestamped task management system APIs
Company: Instacart
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Take-home Project
Quick Answer: This question evaluates a candidate's ability to design and implement time-ordered in-memory data structures and APIs, testing competencies in temporal event ordering and precedence, scheduled deletions, priority-sorted retrieval, user-task assignment, and historical (time-travel) queries.
Constraints
- Up to ~200,000 operations.
- Timestamps fit in 64-bit integers and arrive in non-decreasing order.
- Task IDs and user IDs are strings or integers.
- time_at <= timestamp for getUserTaskNumsAt.
- Expensive operations should be near O(log n).
Examples
Input: ([['createTask', 1, 't1', 'desc1'], ['getTask', 2, 't1'], ['updateTask', 3, 't1', 'desc1-updated'], ['getTask', 4, 't1'], ['getTask', 5, 'tX'], ['deleteTask', 6, 't1'], ['getTask', 7, 't1']],)
Expected Output: ['desc1', 'desc1-updated', None, None]
Explanation: Basic CRUD: create then getTask returns 'desc1'; after updateTask getTask returns the new description; getTask on a missing id 'tX' returns None; after deleteTask, getTask returns None.
Input: ([['createTask', 1, 'a', 'A'], ['createTask', 1, 'b', 'B'], ['createTask', 1, 'c', 'C'], ['updateTaskPriority', 2, 'a', 5], ['updateTaskPriority', 2, 'b', 10], ['updateTaskPriority', 2, 'c', 5], ['getSortedPrioritizedTasks', 3], ['deleteTask', 4, 'b'], ['getSortedPrioritizedTasks', 5]],)
Expected Output: [['b', 'a', 'c'], ['a', 'c']]
Explanation: Sorted retrieval orders by higher priority first, then task_id ascending: b(10) before a(5) and c(5), with a before c on the id tie-break. After deleting b, only a and c remain.
Input: ([['addUser', 1, 'u1'], ['createTask', 1, 't1', 'd1'], ['createTask', 1, 't2', 'd2'], ['assignTaskToUser', 2, 'u1', 't1'], ['assignTaskToUser', 3, 'u1', 't2'], ['unassignTaskToUser', 4, 'u1', 't1'], ['getUserTaskNumsAt', 5, 'u1', 5], ['getUserTaskNumsAt', 6, 'u1', 2], ['getUserTaskNumsAt', 7, 'u1', 3], ['getUserTaskNumsAt', 8, 'u1', 1], ['getUserTaskNumsAt', 9, 'u1', 0]],)
Expected Output: [1, 1, 2, 0, 0]
Explanation: Time-travel: at time_at=5 only t2 is assigned (t1 was unassigned at 4) -> 1; at time_at=2 only t1 had been assigned -> 1; at time_at=3 both t1 and t2 -> 2; at time_at=1 nothing assigned yet -> 0; at time_at=0 (before any op) -> 0.
Input: ([['addUser', 1, 'u1'], ['createTask', 1, 't1', 'd1'], ['assignTaskToUser', 1, 'u1', 't1'], ['scheduleDeletion', 1, 't1', 4], ['getTask', 5, 't1'], ['getUserTaskNumsAt', 5, 'u1', 5], ['getUserTaskNumsAt', 6, 'u1', 4]],)
Expected Output: [None, 0, 1]
Explanation: scheduleDeletion at t=1 with delay 4 deletes t1 at time 5. By the deletion-precedence rule, the deletion fires before the other ops at t=5: getTask returns None and getUserTaskNumsAt(time_at=5) is 0. Querying time_at=4 still sees t1 assigned -> 1.
Input: ([],)
Expected Output: []
Explanation: Edge case: no operations yields no outputs.
Input: ([['assignTaskToUser', 1, 'uX', 'tX'], ['getSortedPrioritizedTasks', 2], ['addUser', 3, 'u1'], ['getUserTaskNumsAt', 4, 'u1', 4], ['getUserTaskNumsAt', 5, 'uNobody', 5]],)
Expected Output: [[], 0, 0]
Explanation: Referencing a non-existent user/task is a no-op; getSortedPrioritizedTasks is empty; a freshly added user has 0 tasks; an unknown user returns 0.
Input: ([['createTask', 1, 't1', 'd1'], ['scheduleDeletion', 1, 't1', 3], ['deleteTask', 2, 't1'], ['createTask', 3, 't1', 'd1-new'], ['getTask', 4, 't1'], ['getTask', 5, 't1']],)
Expected Output: [None, None]
Explanation: A task is scheduled for deletion at t=4, then manually deleted at t=2, then a new task with the same id is created at t=3. At t=4 the scheduled deletion still fires (it targets the id) and removes the recreated task, so both getTask calls return None.
Hints
- Because operations arrive in non-decreasing timestamp order, you can flush all scheduled deletions whose due time is <= the current operation's timestamp BEFORE processing that operation. This enforces the deletion-precedence rule for free.
- Use a min-heap keyed by (due_time, sequence) for scheduled deletions, and mark each scheduled entry alive/cancelled so a manually-deleted task does not double-delete.
- For getUserTaskNumsAt, keep a per-user history of (timestamp, cumulative_count). On each assign/unassign/delete append/overwrite the entry for that timestamp, then answer a query with a binary search for the last entry whose timestamp <= time_at.
- deleteTask must also unassign the task from every user; keep a reverse index task_id -> set(user_id) so you can update each affected user's count.