Build a Removable Timestamp-Ordered Task Scheduler
ByteDanceSoftware Engineer · Technical ScreenLast updated
Design a task scheduler supporting task insertion/update, cancellation by ID, and retrieving ready tasks up to a given time limit using a min-heap with lazy deletion.
Problem Statement
Design a task scheduler that manages tasks scheduled to run at specific timestamps. Each task has a unique integer task_id and an execution timestamp.
Implement the TaskScheduler class:
TaskScheduler(): Initializes an empty scheduler.add_task(task_id: int, timestamp: int) -> None: Adds a task with the giventask_idandtimestamp. Iftask_idalready exists in the scheduler, update its scheduledtimestampto the new value.remove_task(task_id: int) -> bool: Cancels and removes the task withtask_id. ReturnsTrueif the task existed and was removed, orFalseif it was not found.pop_ready_tasks(current_time: int, max_count: int) -> list[int]: Retrieves and removes up tomax_counttasks whosetimestamp <= current_time. Tasks must be returned in ascending order of theirtimestamp. If multiple tasks have the exact sametimestamp, ties must be broken by smallertask_idfirst.
Interface
class TaskScheduler:
def __init__(self):
pass
def add_task(self, task_id: int, timestamp: int) -> None:
pass
def remove_task(self, task_id: int) -> bool:
pass
def pop_ready_tasks(self, current_time: int, max_count: int) -> list[int]:
pass
Examples
Example 1
scheduler = TaskScheduler()
scheduler.add_task(101, 15)
scheduler.add_task(102, 10)
scheduler.add_task(103, 15)
scheduler.add_task(104, 20)
# Pop up to 2 tasks ready by time 15
# Tasks ready: 102 (time 10), 101 (time 15), 103 (time 15)
# Order: 102, then tie between 101 and 103 -> 101 has smaller ID
print(scheduler.pop_ready_tasks(15, 2)) # Output: [102, 101]
# Cancel task 104
print(scheduler.remove_task(104)) # Output: True
print(scheduler.remove_task(999)) # Output: False
# Reschedule task 103 to timestamp 25
scheduler.add_task(103, 25)
# Check ready tasks at time 20
print(scheduler.pop_ready_tasks(20, 5)) # Output: []
# Check ready tasks at time 30
print(scheduler.pop_ready_tasks(30, 5)) # Output: [103]
Constraints
1 <= task_id <= 10^90 <= timestamp, current_time <= 10^121 <= max_count <= 10^5- At most
2 * 10^5total calls will be made across all methods.
Edge Cases to Consider
- Calling
pop_ready_taskswhen no tasks havetimestamp <= current_time. - Overwriting an existing task with a newer or older timestamp.
- Removing a task and adding it again later.
max_countlarger than the total number of ready tasks.
Hints
Hint 1
To efficiently retrieve the smallest timestamp and break ties by task ID, a min-heap storing tuples `(timestamp, task_id)` is a natural choice.
Hint 2
Directly removing or updating elements inside a standard binary heap is `O(n)`. Instead, maintain a hash map of `task_id -> valid_timestamp` to perform lazy deletion.
Hint 3
When popping from the heap, discard any entry whose timestamp does not match the current valid timestamp in the hash map.
Follow-up questions
- How would you ensure thread safety if multiple worker threads were adding, removing, and popping tasks concurrently?
- If many tasks are added and subsequently removed without popping, the heap can accumulate 'zombie' entries and exhaust memory. How would you trigger a compaction or cleanup?
- How would you scale this design to a distributed system where tasks are stored across multiple partitions and must be executed with high availability?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.