Implement a Task Processor
Company: Scale AI
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of priority-based scheduling and dependency resolution, focusing on data structures and graph-ordering concepts required to retrieve the smallest-deadline task while respecting prerequisite subtasks.
Part 1: Task Processor Without Dependencies
Constraints
- 0 <= len(operations) <= 200000
- Each task id is unique across all add operations
- 0 <= deadline <= 10^9
- The number of consume operations can be larger than the number of added tasks
Examples
Input: [('add', {'id': 10, 'deadline': 5}), ('add', {'id': 2, 'deadline': 3}), ('add', {'id': 7, 'deadline': 3}), ('consume',), ('consume',), ('consume',)]
Expected Output: [2, 7, 10]
Explanation: Tasks with deadline 3 are consumed before deadline 5, and id 2 comes before id 7 on the tie.
Input: [('consume',), ('add', {'id': 5, 'deadline': 1}), ('consume',), ('consume',)]
Expected Output: [None, 5, None]
Explanation: Consuming from an empty processor returns None.
Input: [('add', {'id': 3, 'deadline': 4, 'subtasks': [99]}), ('add', {'id': 1, 'deadline': 2, 'subtasks': [3]}), ('consume',), ('add', {'id': 2, 'deadline': 1}), ('consume',), ('consume',)]
Expected Output: [1, 2, 3]
Explanation: The subtasks fields are ignored in Part 1, so only deadlines and ids matter.
Input: []
Expected Output: []
Explanation: No operations means no consume results.
Hints
- You need fast access to the smallest deadline seen so far.
- A min-heap keyed by `(deadline, id)` naturally handles both priority and tie-breaking.
Part 2: Task Processor With Subtask Dependencies
Constraints
- 0 <= len(operations) <= 200000
- Each task id is unique across all add operations
- 0 <= deadline <= 10^9
- The total number of subtask references across all added tasks is at most 200000
- Each task's subtasks list contains distinct ids
- Subtasks may reference tasks added later, and dependency cycles may exist
Examples
Input: [('add', {'id': 1, 'deadline': 2, 'subtasks': [2]}), ('add', {'id': 2, 'deadline': 4, 'subtasks': []}), ('consume',), ('consume',), ('consume',)]
Expected Output: [2, 1, None]
Explanation: Task 1 is blocked until task 2 is consumed.
Input: [('add', {'id': 1, 'deadline': 2, 'subtasks': []}), ('add', {'id': 3, 'deadline': 2, 'subtasks': []}), ('add', {'id': 2, 'deadline': 1, 'subtasks': [3]}), ('consume',), ('consume',), ('consume',)]
Expected Output: [1, 3, 2]
Explanation: Task 2 has the smallest deadline but is blocked. Between eligible tasks 1 and 3, the smaller id is consumed first.
Input: [('add', {'id': 1, 'deadline': 1, 'subtasks': [2]}), ('add', {'id': 2, 'deadline': 2, 'subtasks': [1]}), ('consume',)]
Expected Output: [None]
Explanation: Both tasks are blocked by a cycle, so nothing is eligible.
Input: [('add', {'id': 5, 'deadline': 10, 'subtasks': [6]}), ('consume',), ('add', {'id': 6, 'deadline': 1, 'subtasks': []}), ('consume',), ('consume',)]
Expected Output: [None, 6, 5]
Explanation: The first consume fails because task 5 depends on task 6, which is added later.
Input: []
Expected Output: []
Explanation: No operations means no consume results.
Hints
- Track how many prerequisites each added task still needs before it becomes eligible.
- Use a min-heap for eligible tasks, and when a task is consumed, update every task waiting on it.