In Dart, how do Timer and Timer.periodic differ from Future.delayed, and how do cancel() and isActive let you stop one?
answer
- a handle you can cancel
- one-shot versus repeating
- cancel twice is fine
- isActive false after firing
- tick counts durations, not calls
basics
~20 sTimer runs a callback once after a delay and Timer.periodic repeats it, and both return a handle with cancel() and isActive. Future.delayed also waits but cannot be cancelled, so use a Timer whenever the work might need stopping.
solid answer
~40 s`Timer(duration, callback)` from `dart:async` fires once; `Timer.periodic(duration, (timer) {...})` fires repeatedly until cancelled and passes itself to the callback. Both return a `Timer` handle: `cancel()` guarantees the callback will not run again (calling it twice is allowed), and `isActive` is `true` for a one-shot timer until it fires or is cancelled, and for a periodic timer until it is cancelled. `Future.delayed(duration, computation)` gives you a future instead, with no way to cancel it. Periodic timing is loose: no more than `n` callbacks happen in `n` durations, late ticks are skipped rather than queued, and `tick` counts elapsed durations. `Timer.run(callback)` is `Timer(Duration.zero, callback)`. In Flutter, a `State` that starts a timer must cancel it in `dispose`.
code
dart · 16 linesimport 'dart:async';
class Debouncer {
Debouncer(this.delay);
final Duration delay;
Timer? _timer;
void call(void Function() action) {
_timer?.cancel();
_timer = Timer(delay, action);
}
bool get isPending => _timer?.isActive ?? false;
void dispose() => _timer?.cancel();
}go deeper
Recall Timer for once, Timer.periodic for repeats, cancel() to stop, and that Future.delayed cannot be cancelled.
Explain isActive for one-shot versus periodic timers, what tick counts, skipped ticks, and that callbacks run in the zone where the timer was created.
Audit long-lived code for timers never cancelled on dispose, and protect periodic jobs so one failing tick does not reach the zone handler and end the process.
Treat every timer as an owned resource with a documented lifetime, and prefer designs where polling cadence and cancellation are centralised rather than scattered.
## The Timer API `Timer` (library `dart:async`) schedules a callback on the current isolate's event loop after a delay. It has two constructors and one static helper: | API | Fires | Callback type | |---|---|---| | `Timer(Duration duration, void Function() callback)` | Once, after `duration` | No arguments | | `Timer.periodic(Duration duration, void callback(Timer timer))` | Every `duration`, until cancelled | Receives the timer itself | | `Timer.run(void Function() callback)` | As soon as possible | Same as `Timer(Duration.zero, callback)` | Every constructor returns a **`Timer`** handle with three members: - **`cancel()`** — the callback will not be called again. Calling it more than once is allowed and has no further effect. - **`isActive`** — a one-shot timer is active until its callback has run or it is cancelled; a periodic timer is active until it is cancelled. - **`tick`** — starts at 0 and counts how many durations have passed at the latest event. ## Timer versus Future.delayed `Future.delayed(duration, [computation])` also waits and then runs something, but it gives you a **`Future`**, not a handle. There is no API to cancel it; if the screen closes or the job is aborted, the computation still runs. Rules of thumb: - a one-off wait inside an `async` function (`await Future.delayed(...)` for a retry backoff): `Future.delayed` is fine; - anything that might need to be **stopped** — a debounce, a polling loop, a timeout you clear on success, a heartbeat: use a `Timer`. ## Periodic timing is not exact The SDK promises only that **no more than `n` callbacks run in `duration * n`**. The gap between two callbacks can be shorter or longer than `duration`, depending on when the previous callback ran and how busy the event loop is. If the isolate is blocked long enough that several ticks were due, the missed ones are **skipped**, not replayed; `tick` jumps to reflect elapsed durations rather than the number of calls. A timer is also never faster than the event loop allows — a long synchronous computation delays every timer. When compiled to JavaScript for the web, the browser's minimum granularity is about 4 ms. ## Zones and errors A timer captures the **zone current when it is created** and runs its callback there. If the callback throws, the error goes to that zone's uncaught-error handler — in a server job wrapped in `runZonedGuarded`, to its `onError`; in the root zone of a standalone program, it ends the process. Wrap a periodic job's body in `try`/`catch` if one failed tick should not be fatal. ## Lifecycle discipline 1. Store the handle in a field: `Timer? _poll;`. 2. Cancel before replacing: `_poll?.cancel(); _poll = Timer(...)` — the core of a **debounce**. 3. Cancel on teardown: a Flutter `State` cancels in `dispose()`, a service in its own `dispose()`. A periodic timer that is never cancelled keeps its callback — and everything it captures — alive and running for the life of the isolate. 4. Stop a periodic timer from inside by calling `timer.cancel()` on the argument its callback receives.
- What does timer.tick report if a periodic timer's callback was delayed past several intervals?It reports the number of durations that have elapsed, not the number of callbacks. The missed ticks are skipped rather than delivered late, so after a long block `tick` can jump, for example from 1 to 4.
- In which zone does a Timer's callback run, and why does it matter?In the zone that was current when the timer was created: the constructor binds the callback to that zone. So an exception thrown by the callback goes to that zone's uncaught-error handler — a `runZonedGuarded` `onError` if the timer was started inside one, otherwise the root zone, which ends a standalone Dart program.
saying these in an interview costs you the question
- Future.delayed can be cancelled by ignoring its result.
- Timer.periodic fires exactly every duration, catching up missed ticks.
- Calling cancel() twice on a Timer throws.
- A periodic timer stops by itself when its State is disposed.
- timer.tick counts how many times the callback has run.