skip to content

What is the difference between scheduleAtFixedRate and scheduleWithFixedDelay, and how does task duration affect each?

level: middleimportance: must knowfreq 70%

answer

  1. rate = start-to-start; delay = end-to-start
  2. slow task: rate piles up back-to-back, delay keeps the gap
  3. rate effective period -> task duration when slow
  4. delay effective period = duration + delay
  5. neither overlaps a single periodic task

basics

~20 s

FixedRate measures the period from the start of one run to the start of the next, so it tries to run every N units. FixedDelay measures the gap from the end of one run to the start of the next. If a task runs longer than the rate period, fixedRate runs do not overlap (they queue back-to-back) while fixedDelay always leaves the full gap.

solid answer

~60 s

Both repeat a task, but they measure time differently. scheduleAtFixedRate(task, initialDelay, period, unit) anchors each execution to a fixed timeline: run k is scheduled for initialDelay + k*period from the start, independent of how long the task takes. scheduleWithFixedDelay(task, initialDelay, delay, unit) measures the gap from the *end* of one run to the *start* of the next, so the cadence stretches with task duration. The key behavioral split is what happens when the task takes longer than the period: with fixedRate the next run is already due, so it starts immediately after the previous one finishes (runs effectively back-to-back, never concurrently for a single periodic task — the executor serializes a given periodic task), which can produce bursts to 'catch up'. With fixedDelay there is no catch-up: you always get exactly the configured idle gap. Use fixedRate when you care about throughput/cadence (e.g., 'sample every second'); use fixedDelay when you care about a guaranteed rest between runs (e.g., 'wait 5s after each poll completes') and want to avoid pile-up.

code

java · 7 lines
java
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();

// Targets t = 0,1,2,3... seconds; if a run overruns, the next starts ASAP (no overlap).
exec.scheduleAtFixedRate(this::sample, 0, 1, TimeUnit.SECONDS);

// Always leaves a 1s gap AFTER each run finishes; effective period = work + 1s.
exec.scheduleWithFixedDelay(this::poll, 0, 1, TimeUnit.SECONDS);

go deeper

for a junior

Can state the one-line difference: rate is start-to-start, delay is end-to-start.

for a middle

Draws the timeline for a slow task: fixedRate goes back-to-back/bursts to catch up, fixedDelay always keeps the configured gap; picks the right one for a given requirement.

for a senior

Explains serialization of a single periodic task, drift characteristics, pile-up/back-pressure risk with fixedRate, and self-throttling with fixedDelay; ties choice to throughput vs. rest semantics.

for a principal

Designs around overrun: monitors lag, bounds catch-up, decides when fixedRate's catch-up is harmful (load amplification) vs. helpful (cadence), and when to move to a dedicated scheduler with misfire policies.

## Setup Both methods live on `ScheduledExecutorService` and repeat a `Runnable`: - `scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit)` - `scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit)` `initialDelay` is how long to wait before the *first* run. The difference is entirely in how the *subsequent* runs are timed. ## Fixed rate = anchored to a clock timeline With **fixed rate**, execution *n* is targeted for time `initialDelay + n*period` (measured from when you scheduled it). The schedule is a fixed grid of tick marks: 0s, 1s, 2s, 3s, … (for period = 1s). The task's own duration is, in principle, ignored when computing the next target time. Example, period = 1s, each task takes 0.2s: ``` | run0 ...0.2 | idle 0.8 | run1 ...1.2 | idle 0.8 | run2 ... ^0s ^1s ^2s ``` Runs start at ~0s, 1s, 2s — exactly on the grid. ## What happens when the task is slow (fixed rate) The interesting case: period = 1s but the task takes **1.5s**. Run 0 starts at 0s, finishes at 1.5s. Run 1 was *due* at 1s — already late. The executor does **not** run two copies of the same periodic task at once; it serializes them. So run 1 starts immediately at 1.5s (the moment run 0 finishes), finishes at 3.0s; run 2 was due at 2s, also overdue, so it starts at 3.0s, and so on. The result is **back-to-back execution with no idle time** — the scheduler is perpetually trying (and failing) to catch up. The average period degrades to the task duration, and if the task occasionally speeds up you can get a **burst** of catch-up runs. ## Fixed delay = anchored to the previous finish With **fixed delay**, the next run starts `delay` after the previous run **completes**. The formula is recursive: `start(n+1) = end(n) + delay`. Task duration therefore pushes the whole future schedule later. Example, delay = 1s, each task takes 1.5s: ``` | run0 ...1.5 | idle 1.0 | run1 ...1.5 | idle 1.0 | run2 ... ^0s ^2.5s ^5.0s ``` There is **always** a full 1s of rest between runs, no catch-up, no bursts. The effective period is `taskDuration + delay`. ## Choosing between them - Want a steady **cadence/throughput** ("emit a heartbeat every second", "sample a sensor 10x/s")? Use **fixed rate** — but make sure the task reliably finishes within the period, or you will get pile-up/bursts. - Want a guaranteed **gap/rest** between runs, or the task duration is variable and you must not overlap or pile up ("poll an API, then wait 30s after each poll finishes")? Use **fixed delay** — it is self-throttling. ## Shared caveats - Neither runs two copies of the *same* periodic task concurrently; a slow run delays the next. - Drift: real schedulers are not perfectly precise; fixed rate bounds long-term drift to the grid, fixed delay lets drift accumulate by design. - An uncaught exception silently cancels all future runs of *either* — wrap the body in try/catch.

  • If a fixedRate task with period 1s occasionally takes 5s, what cadence do you actually observe?
    During the slow run you fall behind; the runs that came due during those 5s are overdue, so when the slow run finishes they fire back-to-back (a short burst) before settling back onto the 1-second grid. The scheduler never runs two copies of the same task at once, so the burst is serial, not concurrent.
  • You must guarantee a job never starts again until the previous run is fully done plus a cool-down. Which method, and why?
    scheduleWithFixedDelay. It measures the gap from the end of the previous run, so the cool-down is always honored and runs can never pile up — unlike fixedRate, which tries to catch up and can run back-to-back with no rest.

Fixed rate is a bus timetable: a bus is supposed to leave every 10 minutes no matter how late the last one was, so delays cause buses to bunch up. Fixed delay is 'rest 10 minutes after each shift ends' — the break is always honored, but the schedule drifts later as shifts run long.

saying these in an interview costs you the question

  • Saying fixedRate runs two copies of the same task concurrently when it overruns — the executor serializes a given periodic task.
  • Claiming fixedDelay measures from the start of the previous run (it measures from the end).
  • Thinking fixedRate guarantees exact spacing even for slow tasks — it produces catch-up bursts instead.
  • Assuming both behave identically when the task duration is shorter than the period (they do look similar there — the difference only shows under load).

context