Task Scheduler with Dependencies
Reported by candidates from Scale AI's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.
The mistake that sinks most first attempts at this Scale AI task scheduler is trusting the heap's top entry after a deadline update. Scale AI candidates reported it in July 2026, and it's a three-part build: add tasks, consume the earliest-deadline one, then layer on subtask dependencies and UPDATE. It's a priority queue problem with bookkeeping on top. If you've got an OA invite, expect to simulate an operation list and return every CONSUME result. StealthCoder sits invisibly as a safety net if you blank mid-assessment, but the pattern below should get you most of the way.
The problem
FastPrep turns the reported three-step interview progression into a runnable operation-list simulation: ADD models addTasks, CONSUME models consumeTask, and UPDATE models updateDeadline. Core match: 85-90%. The main mechanics are preserved: earliest-deadline heap consume, subtask dependencies, deadline updates for unconsumed tasks, and stale heap entries skipped during consume. Literal match: 80-85%. Since the source was a short interview recap, FastPrep made the runnable details explicit, including "NONE" when no task is available, tie-breaking by taskId, integer deadline comparison, unique task ids, no cycles, constraints, and extra corner-case examples. Original Interview Progression Part 1: Implement addTasks and consumeTask when there are no dependencies. consumeTask should return the available task with the earliest deadline. Part 2: Extend the scheduler so a task may list subtasks. The task can be consumed only after every subtask has been consumed. Part 3: Add updateDeadline(taskId, newDeadline). If the task has not been consumed, its deadline can change. consumeTask must ignore stale priority entries created before the update. Problem Statement You are implementing a task scheduler. Each task has a unique taskId, an integer deadline, and zero or more subtasks. A task with subtasks cannot be consumed until every listed subtask has already been consumed. Simulate the scheduler with an operation list and return the result of every CONSUME operation. Function Signature public List<String> processOperations(List<List<String>> operations) Operation Format ["ADD", taskId, deadline] ["ADD", taskId, deadline, subtask1, subtask2,...] ["CONSUME"] ["UPDATE", taskId, newDeadline] Rules A task can be consumed at most once. A task is available only after all of its subtasks have already been consumed. A referenced subtask may be added before or after the task that depends on it. If a referenced subtask is never added and consumed, the parent task remains unavailable. Among available tasks, CONSUME returns the task with the smallest current deadline. Deadlines are encoded as strings in the input, but they must be compared as integers. If multiple available tasks have the same deadline, return the lexicographically smaller taskId. If no task is available when CONSUME is called, return "NONE". UPDATE has no effect if the task does not exist or has already been consumed. Updating a blocked but unconsumed task is allowed. Its new deadline should be used once it becomes available. Each taskId is added at most once. You may assume there are no cyclic dependencies. Function processOperations(operations: List<List<String>>) → List<String> Examples Example 1 operations = [["ADD", "A", "5"], ["ADD", "B", "2"], ["CONSUME"], ["CONSUME"]] return = ["B", "A"] Both tasks are available immediately. Task B has the earlier deadline. Example 2 operations = [["ADD", "A", "5"], ["ADD", "B", "2"], ["ADD", "C", "1", "A", "B"], ["CONSUME"], ["CONSUME"], ["CONSUME"]] return = ["B", "A", "C"] C has the smallest deadline, but it depends on A and B, so it cannot be consumed until both are done. Example 3 operations = [["ADD", "A", "5"], ["ADD", "B", "2"], ["UPDATE", "B", "10"], ["CONSUME"], ["CONSUME"]] return = ["A", "B"] B was available with deadline 2, then its deadline changes to 10. The scheduler must not consume the stale old heap entry for B. Example 4 operations = [["ADD", "A", "10"], ["ADD", "B", "5", "A"], ["UPDATE", "B", "1"], ["CONSUME"], ["CONSUME"]] return = ["A", "B"] The update to B is valid because B has not been consumed, but B remains blocked until A is consumed. Example 5 operations = [["ADD", "C", "9"], ["ADD", "A", "9"], ["ADD", "B", "10"], ["CONSUME"], ["CONSUME"], ["CONSUME"]] return = ["A", "C", "B"] Deadlines are parsed as integers, so 9 is before 10. Between A and C with the same deadline, A is returned first. Example 6 operations = [["ADD", "A", "3", "B"], ["CONSUME"], ["ADD", "C", "1"], ["CONSUME"], ["CONSUME"]] return = ["NONE", "C", "NONE"] A depends on B. Since B is never added and consumed, A never becomes available. Constraints 1 <= operations.length <= 100000 1 <= number of added tasks <= 100000 0 <= total number of subtask references <= 200000 1 <= deadline <= 1000000000 taskId is a non-empty alphanumeric string with length at most 32. Each taskId appears in ADD at most once. The dependency graph has no cycles.
Reported by candidates. Source: FastPrep
Pattern and pitfall
The trick is lazy deletion. Keep a min-heap of (deadline, taskId), compared as integers, with taskId as the tiebreak. Keep a map of current deadlines, a consumed set, and for each task a count of unconsumed subtasks plus a reverse map from subtask to parents. When a task has zero pending subtasks, push it onto the heap. On UPDATE, if the task exists and isn't consumed, change the map and push a fresh entry if it's already available. On CONSUME, pop until the entry's deadline matches the current map and the task isn't consumed. Otherwise return NONE. The pitfalls: comparing deadlines as strings, so 10 sorts before 9, pushing a blocked task onto the heap early, and forgetting that a subtask can be added after its parent. A parent whose subtask never gets added stays blocked forever. StealthCoder is the hedge on the live OA if the dependency counting trips you up.
Drill it cold or hedge it with StealthCoder. Either way, don't walk into the OA hoping you remember the trick.
You can drill Task Scheduler with Dependencies cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Made for the candidate who got the OA invite this morning and has 72 hours, not six months.
Get StealthCoderRelated leaked OAs
You've seen the question.
Make sure you actually pass Scale AI's OA.
Scale AI reuses patterns across OAs. Made for the candidate who got the OA invite this morning and has 72 hours, not six months. Works on HackerRank, CodeSignal, CoderPad, and Karat.
Task Scheduler with Dependencies FAQ
What's the core trick in the Scale AI task scheduler problem?+
Lazy deletion on a min-heap. Never remove old entries when a deadline changes. Push a new entry, and on CONSUME pop until the top matches the task's current deadline and it isn't consumed. That handles stale entries from UPDATE cleanly without a custom heap.
How do I handle subtasks added after their parent?+
Track dependencies by id, not by object. Store each parent's list of required subtasks, and count how many aren't consumed yet. A subtask that was never added counts as pending. When a subtask is consumed, decrement its parents' counts and push any that hit zero.
Why do my tie-breaks or ordering come out wrong?+
Usually deadlines are compared as strings. They arrive as strings, so parse them to integers first. Then break ties by lexicographically smaller taskId. In the examples, 9 comes before 10, and A comes before C at deadline 9.
What should UPDATE do on a blocked task?+
Update the stored deadline but don't push it onto the heap yet. When its subtasks are all consumed, push it with the current deadline. UPDATE on a missing or already consumed task does nothing. Example 4 shows this: B stays blocked until A is consumed.
How do I prepare for this in 48 hours?+
Write the solution once from scratch with a heap, a deadline map, a consumed set, and a pending-count map. Then run all six examples by hand, especially the NONE case and the stale-entry case. Practice the operation-list parsing so you don't fumble it live.