Why this matters: the heap-and-dispatcher core is deliberately small, which means almost every real-world demand lands as a question about where the design bends. Each variation below has a seam waiting for it — and knowing which seam, before the interviewer asks, is what the trade-offs act grades.
Cron expressions: a schedule source, not a parser in the dispatcher
Real schedules arrive as things like "weekdays at 02:30." The wrong move is teaching the dispatcher to understand cron syntax; the right one is noticing that the core only ever asked a schedule two questions — first fire from now? and next fire after this one? — and formalizing that pair as the schedule-source seam. Fixed-time, interval, and cron are then three implementations of one small interface, and cron's parsing complexity stays quarantined in its own corner with its own tests. This is the same move the parking lot made with pricing: the core consumes answers, not formats.
Jitter: de-synchronizing the herd
Schedule a hundred tasks "every hour" and they all fire at the top of the hour — a self-inflicted thundering herd against whatever those tasks touch. The fix is jitter: offset each computed fire by a small deterministic-per-task amount (a hash of the task id into ±N seconds, not fresh randomness — reproducibility matters for debugging). The design point worth saying: jitter belongs inside the schedule source's next-fire computation, not sprinkled into the dispatcher — the dispatcher's contract stays "fire what's due," and what "due" means stays a schedule concern.
Fairness between competing DAGs
Two pipelines share the scheduler; both become ready at once and each submits fifty tasks. The provided pool drains them in arrival order, so pipeline A's burst can starve pipeline B's start. If the requirement arrives, the seam is between readiness and submission: instead of submitting immediately, ready tasks enter per-owner ready queues drained round-robin (or by weight). Cost: a new layer with its own state, plus the pool's queue no longer reflects true arrival order. The honest framing is that this is admission scheduling layered on top — and below the pool's own queue — and it should exist only when a real starvation complaint exists, because every layer between "ready" and "running" adds latency to diagnose.
Pause and resume
Operators want to pause a schedule without cancelling it. The state machine absorbs this cleanly — a paused flag consulted at dispatch: a paused task popped from the heap is not run, but its recurrence still computes and reinserts the next fire, so resume means "future fires run again" with no catch-up. The subtle call is what pausing means for missed fires, and lesson 01's rule already answered it: skip. Pause is then just a standing instance of the same policy, which is the kind of consistency worth pointing out in a round.
Observability: what a scheduler owes its operators
A scheduler is infrastructure other teams blame first. Three read-only surfaces settle most arguments: a next-fires table (the heap's top N with task ids and times — "is it scheduled?"), per-task last-run records (when, how long, outcome — "did it run?"), and a lateness metric (dispatch time minus intended fire time — "is the scheduler behind, or is the pool?"). That last one earns its place in an interview: it cleanly splits scheduling delay from execution delay, which is exactly the boundary this design drew on day one.
Key takeaway
Every variation lands on a seam the core already drew: cron behind the schedule-source interface, jitter inside next-fire computation, fairness as an admission layer between ready and submitted, pause as a dispatch-time flag riding the existing skip policy, and observability that splits scheduling lateness from execution lateness. Knowing the seam before the question arrives is the trade-offs act.