Dart Timer
last modified May 29, 2026
Introduction
The Timer class from dart:async lets you schedule
code to run after a delay or repeatedly at fixed intervals. It is the
foundation of all time-based operations in Dart: countdowns, polling loops,
debounced inputs, and animation frames all rely on timers under the hood.
Dart provides two timer variants:
- One-shot timer — runs a callback once after a specified
Duration. - Periodic timer — runs a callback repeatedly with a fixed interval between executions.
Both return a Timer object that can be cancelled at any time
before the callback fires. Timers work with the event loop: they do not
block the main thread but instead schedule their callbacks to run in a
future microtask or event loop tick, keeping the application responsive.
How Timers interact with the event loop
Dart’s event loop runs continuously, picking up events from queues. When a timer fires, its callback is added to the event queue (not the microtask queue). This means the callback runs after all pending microtasks have completed and the current event loop tick finishes. Because timers use the event queue, they are safe for non-blocking work — but you should still avoid heavy computation inside timer callbacks to keep the UI smooth.
import 'dart:async';
void main() {
print('Start');
// Timer callback goes to event queue
Timer(const Duration(seconds: 0), () => print('Timer callback'));
// Microtask runs before the timer callback
scheduleMicrotask(() => print('Microtask 1'));
// Future immediate also schedules a microtask
Future.microtask(() => print('Microtask 2'));
print('End');
// Output:
// Start
// End
// Microtask 1
// Microtask 2
// Timer callback
}
Even a zero-duration timer does not run immediately. It waits until the current execution context yields and the event loop picks up the timer event. This ordering is important when you need to defer a computation without blocking the microtask queue.
One-shot Timer
Create a one-shot timer with Timer(Duration delay, void
Function() callback). The callback executes once after
delay has elapsed.
import 'dart:async';
void main() {
print('Waiting for the timer…');
final timer = Timer(const Duration(seconds: 2), () {
print('2 seconds have passed!');
});
// The program stays alive until the timer fires
// (or press Enter to quit early)
stdin.listen((_) => timer.cancel());
}
The timer callback runs on the event loop; while the timer is pending, the program remains alive. If the callback is the last piece of work, the isolate may exit immediately after it finishes.
Periodic Timer (Timer.periodic)
Timer.periodic(Duration interval, void Function(Timer timer)
callback) creates a timer that fires every interval
until cancelled. The callback receives the timer itself, so you can
cancel it from within.
import 'dart:async';
void main() {
int tick = 0;
final timer = Timer.periodic(const Duration(seconds: 1), (timer) {
tick++;
print('Tick $tick');
if (tick >= 5) {
timer.cancel(); // stop after 5 ticks
print('Timer stopped.');
}
});
print('Periodic timer started.');
}
The interval is measured from the start of one callback invocation to the start of the next. If a callback takes longer than the interval, the next invocation is skipped and will fire after the next full interval.
Cancelling a timer
Both Timer and Timer.periodic return a
Timer object with a cancel() method. Calling
cancel() prevents any future callbacks from executing. It
is safe to call cancel() multiple times; after the first
call, subsequent invocations have no effect.
import 'dart:async';
void main() {
final timer = Timer(const Duration(seconds: 5), () {
print('This will never print.');
});
// Cancel immediately – the callback is never called.
timer.cancel();
print('Timer cancelled before it fired.');
}
Cancelling a timer is the recommended way to clean up when the result of the delayed work is no longer needed, for example when a widget is removed from the screen or a search query is replaced.
Timer vs Future.delayed
Future.delayed also runs code after a delay, but it returns a
Future that completes when the callback runs. A
Timer is a lower-level construct; you cannot
await it directly and it does not produce a value.
| Feature | Timer | Future.delayed |
|---|---|---|
| Returns | Timer object (for cancellation) | Future<void> (or a typed value) |
| Cancellation | Yes, via cancel() | No built-in cancellation (but you can use a Completer) |
| Awaitable | No | Yes |
| Periodic | Yes (Timer.periodic) | No (must recreate) |
Use Future.delayed when you need to delay an async operation
and want to await the result. Use Timer when you
need cancellation or a repeating schedule.
Countdown timer
A countdown uses a periodic timer that decrements a counter every second and stops when reaching zero.
import 'dart:async';
void countdown(int seconds) {
int remaining = seconds;
final timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (remaining > 0) {
print('$remaining...');
remaining--;
} else {
timer.cancel();
print('Liftoff!');
}
});
}
void main() {
countdown(5);
}
The periodic callback fires every second, printing the remaining seconds. After reaching zero, the timer cancels itself and prints a final message.
Periodic data polling
A common use case is polling a remote API for updates. The example below simulates a fetch every two seconds and stops after a set number of polls.
import 'dart:async';
// Simulate an async API call
Future<String> fetchStatus() async {
await Future.delayed(const Duration(milliseconds: 200));
return DateTime.now().millisecondsSinceEpoch.toString();
}
void main() {
int pollCount = 0;
const maxPolls = 5;
final timer = Timer.periodic(const Duration(seconds: 2), (timer) async {
final status = await fetchStatus();
pollCount++;
print('Poll $pollCount: $status');
if (pollCount >= maxPolls) {
timer.cancel();
print('Polling stopped after $maxPolls requests.');
}
});
}
Even though the callback is marked async, the periodic timer
does not wait for the future to complete before starting the next tick. If
the fetch takes longer than the interval, multiple requests may overlap. For
production use, consider adding a flag to avoid concurrent polls.
Debounce using Timer
In UI applications, you often want to delay an action until the user stops typing. A debouncer resets a timer on every keystroke.
import 'dart:async';
class Debouncer {
final Duration delay;
Timer? _timer;
Debouncer({required this.delay});
void call(void Function() action) {
_timer?.cancel();
_timer = Timer(delay, action);
}
void dispose() => _timer?.cancel();
}
void main() {
final debouncer = Debouncer(delay: const Duration(milliseconds: 300));
// Simulate rapid keystrokes
const keystrokes = ['H', 'e', 'l', 'l', 'o', '!'];
for (final char in keystrokes) {
debouncer.call(() => print('Search query: $char'));
}
// After 300ms of silence, the last action fires once.
// Output (after delay):
// Search query: !
}
The debouncer cancels any pending timer each time
call is invoked, ensuring that the action runs only after a
quiet period. This pattern is ubiquitous in search fields and auto-save
features.
Multi-purpose timer utility
The program below combines a countdown, a periodic logger, and a cancellable delay into a single script that demonstrates the core timer concepts.
import 'dart:async';
import 'dart:io';
void main() {
stdout.writeln('=== Dart Timer Demo ===\n');
// 1. One-shot timer
stdout.writeln('Starting one-shot timer (3 seconds)...');
final oneShot = Timer(const Duration(seconds: 3), () {
stdout.writeln('One-shot timer fired!');
});
// 2. Periodic timer (countdown)
int count = 5;
stdout.writeln('Countdown:');
final periodic = Timer.periodic(const Duration(seconds: 1), (timer) {
if (count > 0) {
stdout.writeln(count);
count--;
} else {
stdout.writeln('Blast off!');
timer.cancel();
}
});
// 3. Cancellable delay: user can press Enter to abort
final delayTimer = Timer(const Duration(seconds: 10), () {
stdout.writeln('10-second delay elapsed (or was cancelled).');
});
stdout.writeln('Press Enter to cancel the 10-second timer...');
stdin.listen((_) {
delayTimer.cancel();
stdout.writeln('Delay timer cancelled by user.');
oneShot.cancel(); // also clean up the one-shot
periodic.cancel(); // and periodic
exit(0);
});
}
The script starts a one-shot timer, a periodic countdown, and a long delay that the user can cancel by pressing Enter. It illustrates creation, cancellation, and the interaction of multiple timers running simultaneously.
Source
Timer class documentation, Dart event loop guide, Dart Futures tutorial
In this tutorial we covered the Timer class: one-shot and
periodic timers, their relation to the event loop, cancellation,
comparison with Future.delayed, and practical examples
including a countdown, polling, and a debouncer.
Author
List all Dart tutorials.