Explain the difference between @Scheduled fixedRate, fixedDelay, and initialDelay. When do fixedRate executions overlap or pile up?
answer
- fixedDelay = end→start (never overlaps)
- fixedRate = start→start (steady cadence)
- initialDelay = first run only
- single-thread → fixedRate piles up back-to-back
- String variants for ${properties}
basics
~20 sfixedDelay measures the gap between the end of one run and the start of the next, so runs never overlap. fixedRate measures from the start of one run to the start of the next, regardless of how long the run takes. initialDelay delays only the very first execution.
solid answer
~50 sfixedDelay counts from the completion of the previous invocation to the start of the next, so a slow task simply pushes the next run later — executions never overlap. fixedRate counts from the start of one invocation to the start of the next, aiming for a steady cadence. The key gotcha: with the default single-threaded scheduler, if a fixedRate task takes longer than its period, the next execution cannot start on time; it does not run concurrently, it queues and fires immediately after the previous one finishes, so runs bunch up (back-to-back) rather than truly overlapping. Only a multi-threaded TaskScheduler would let them overlap. initialDelay (or initialDelayString) delays just the first execution after startup and combines with either strategy. Values are milliseconds by default; the String variants accept property placeholders and, in modern Spring, ISO-8601/Duration text plus a timeUnit attribute.
code
java · 15 lines@Component
public class Jobs {
// Steady cadence: aim to start every 5s from previous start
@Scheduled(fixedRate = 5000, initialDelay = 2000)
public void sample() { /* independent, short work */ }
// Self-throttling: 5s pause AFTER each run finishes
@Scheduled(fixedDelay = 5000)
public void drainQueue() { /* slow work — never overlaps */ }
// Property-driven + explicit unit (Spring 6)
@Scheduled(fixedDelayString = "${cleanup.delay:30}", timeUnit = TimeUnit.SECONDS)
public void cleanup() { }
}go deeper
Knows end-to-start vs start-to-start distinction.
Explains the single-threaded pile-up behavior and String/property variants.
Ties overlap behavior to pool size and picks the right strategy per workload.
Discusses catch-up semantics, monotonic-clock drift, and self-throttling design tradeoffs.
**The three timing attributes.** - **`fixedRate`** — the interval is measured **start-to-start**. Spring tries to begin a new invocation every N ms measured from when the *previous one began*. Intent: a fixed frequency (e.g. 'poll 10x/sec'). - **`fixedDelay`** — the interval is measured **end-to-start**. Spring waits N ms after the previous invocation *finishes* before starting the next. Intent: a fixed *pause* between runs, so a slow run just delays the following one. - **`initialDelay`** — how long to wait before the **first** invocation only. It layers on top of either `fixedRate` or `fixedDelay`. Useful to avoid a thundering herd of jobs all firing the instant the app starts, or to let the context warm up. **Concrete timeline.** Task takes 3s, period/delay = 5s. - `fixedRate=5000`: starts at t=0, 5, 10, 15… (start-to-start 5s), each finishing 3s later. The 2s of idle is the slack. - `fixedDelay=5000`: starts at t=0 (ends t=3), next at t=8 (ends 11), next at 16… — always a 5s *gap after finishing*. **The pile-up gotcha (the interview favorite).** Task now takes 8s, `fixedRate=5000`. Ideal start times are 0,5,10,15. But the **default scheduler is single-threaded** (one `TaskScheduler` thread). At t=5 the previous run is still executing, so the scheduler cannot run it concurrently. It does not skip and it does not overlap — the missed execution is run **immediately** when the thread frees up at t=8, then the next at t=16 minus catch-up, etc. Net effect: runs go **back-to-back with no gap**, and the task effectively runs as fast as it can. People wrongly say 'fixedRate runs overlap' — they only overlap if you provide a **multi-threaded** `TaskScheduler`/`ThreadPoolTaskScheduler` with pool size > 1. With `fixedDelay` this pile-up is impossible by definition, because the next start is anchored to the previous *finish*. **String variants and units.** `fixedRateString`, `fixedDelayString`, `initialDelayString` take a `String` so you can inject a property: `@Scheduled(fixedDelayString = "${job.delay:5000}")`. Modern Spring (5.3+/6) also parses ISO-8601 duration text (`"PT5S"`, `"5s"`) and offers a `timeUnit` attribute so `@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)` is legal. Bare numeric values default to **milliseconds**. **Mutual exclusivity.** You specify exactly one of `fixedRate`/`fixedDelay`/`cron` per annotation (you may not mix `cron` with a fixed-delay/rate); `initialDelay` is only valid with `fixedRate`/`fixedDelay`, not `cron`. Violations fail at startup. **When to use which.** Use `fixedDelay` when each run's work depends on the previous finishing (cleanup, draining a queue) — it self-throttles. Use `fixedRate` when you want steady sampling and each run is independent and short. Use `cron` for wall-clock schedules ('every day at 02:00'). Use `initialDelay` to stagger startup.
- With fixedRate=5000 and the default scheduler, a run takes 8 seconds. Do two runs execute at the same time?No. The default scheduler is single-threaded, so the delayed run cannot start until the previous finishes; missed runs execute immediately back-to-back rather than concurrently. True overlap requires a multi-threaded TaskScheduler with pool size > 1.
- Which strategy guarantees a task never runs concurrently with itself, and why?fixedDelay — its next start is anchored to the previous invocation's completion, so by construction there is always the configured gap after finishing and no overlap, regardless of pool size.
saying these in an interview costs you the question
- Saying fixedRate lets a slow task overlap on the default (single-threaded) scheduler
- Confusing which measures end-to-start vs start-to-start
- Thinking initialDelay repeats on every run instead of only the first
- Believing bare fixedRate values are seconds (they are milliseconds)