Design and Implement an In-Memory Job Scheduler for Delayed Jobs
Company: Walmart Labs
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: hard
Interview Round: Onsite
Design and implement a job scheduler: a component that accepts jobs, each a piece of code together with when it should run, holds each job until its time comes, and then runs it. The task was asked in a coding round, so treat it as an implementation exercise: write working code for the core and explain your design choices as you go, rather than drawing a distributed architecture.
A possible interface (agree on the real one with your interviewer):
```python
from typing import Callable
class JobScheduler:
def __init__(self, num_workers: int): ...
def schedule(self, job: Callable[[], None], delay_seconds: float) -> int: ...
def cancel(self, job_id: int) -> bool: ...
def shutdown(self) -> None: ...
```
- `schedule` registers `job` to run once, `delay_seconds` from now, and returns an id for it.
- `cancel` keeps a job that has not started from running, and reports whether it did.
- `shutdown` stops the scheduler.
```hint Keep the next job within reach
Most of the time the only question is which job is due next and how long until then. Choose a structure that answers it without scanning every pending job.
```
```hint Do not sleep past an earlier job
Whatever waits for the next due time must not miss a job that is submitted during the wait with an earlier time than the one it is waiting for.
```
### Constraints and Clarifications
- Assume the scheduler lives in a single process, and jobs are code in that same process.
- Not specified: whether jobs repeat, how many may run at once, what happens when a job fails, and whether pending jobs must survive a restart. Ask, or state your assumptions.
### Clarifying Questions
- Are jobs one-off only, or can they repeat at a fixed interval or on a calendar schedule?
- Should jobs run in parallel on a pool of workers, and what happens when more jobs are due than there are free workers?
- In what order should jobs due at the same moment run?
- When a job raises an error, should the scheduler retry it, log it, or stop?
- Can `schedule` and `cancel` be called from several threads at once, including from inside a running job?
- Can a job be cancelled after it has started, and what should `shutdown` do with jobs that are pending or running?
- Must pending jobs survive a process restart, or is in-memory state enough?
### What a Strong Answer Covers
- Clarifies repetition, concurrency, ordering, failure handling and persistence before coding
- A structure ordered by due time, and a dispatcher that waits for the next due time instead of polling, but wakes up for an earlier job
- Thread safety: one lock guarding the shared state, and jobs never run while it is held or on the dispatcher thread itself
- Correct cancellation, shutdown and isolation of failing jobs, with a deterministic order for jobs due at the same time
- The cost of schedule, cancel and dispatch, and how the design can be tested
### Follow-up Questions
- Add jobs that repeat every N seconds. How do you compute each next run time so that slow jobs or late dispatch do not make the schedule drift?
- A few long-running jobs occupy every worker. How do you keep other due jobs from waiting behind them, and how would you notice the delay?
- The scheduler must survive a process restart. What do you store, and at which moments?
- Two copies of the service now run for availability. How do you keep a job from running twice?
Overview: Design and implement an in-memory job scheduler that runs submitted jobs after a delay and supports cancellation and shutdown. It tests choosing a structure ordered by due time, thread-safe coordination without busy waiting, worker concurrency, failure isolation and the trade-offs of recurring or persistent jobs.