Build a Concurrent Recurring Job Scheduler
NuroSoftware Engineer · Technical ScreenLast updated
This problem tests your ability to design a thread-safe, concurrent task scheduling system that manages recurring executions while handling lifecycle events like cancellation and graceful shutdown.
Problem Statement
Design a JobScheduler class that allows clients to submit recurring tasks to be executed at a fixed interval. Your scheduler must support high concurrency, ensuring that adding or removing jobs does not corrupt internal state or cause memory leaks.
Requirements
submit(task: Callable, interval_ms: int) -> JobID: Schedules a task to run repeatedly everyinterval_ms. Returns a unique identifier.cancel(job_id: JobID): Stops the job associated with the ID. If the job is currently running, the execution should be allowed to complete, but no future iterations should trigger.shutdown(): Stops all running jobs and prevents new submissions. The system should wait for active tasks to complete.
Constraints
- Tasks must run independently; a slow task should not delay the scheduling of other tasks.
- You must handle "cancellation races": a job should not be rescheduled if
cancel()is called while it is executing. - Assume
interval_msis at least 10ms.
Example
# Execution flow
scheduler = JobScheduler(num_workers=4)
job_id = scheduler.submit(lambda: print("Tick"), 1000)
# ... some time later ...
scheduler.cancel(job_id)
Considerations
- How will you track the next scheduled execution time for each job?
- How do you ensure that
shutdown()does not result in a deadlock? - How do you isolate exceptions occurring within a task so they don't crash the scheduler?
Hints
Hint 1
Use a priority queue (or a heap) to store tasks ordered by their next execution time, protected by a mutex.
Hint 2
Maintain a separate dictionary mapping `JobID` to task metadata to allow O(1) lookups for cancellation.
Hint 3
Use a dedicated worker pool (e.g., `ThreadPoolExecutor`) to execute tasks, ensuring the scheduler loop itself remains non-blocking.
Follow-up questions
- How would you modify the system to handle tasks that take longer than their `interval_ms` (i.e., overlapping executions)?
- If the scheduler process crashes, how could you persist the schedule to recover exactly where you left off?
- How would you implement a 'jitter' feature to prevent a thundering herd problem when many jobs are scheduled at the same time?
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.