dart
Future.delayed
asynchronous programming
Flutter
Dart language

Behaviour of Future.delayed in dart

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Future.delayed schedules work to complete later, but "later" in Dart has precise event-loop semantics. Most confusion comes from mixing it up with microtasks, assuming Duration.zero means immediate execution, or forgetting that await pauses only the current async function rather than the whole isolate.

What Future.delayed Actually Does

Future.delayed creates a future that completes after a timer fires. If you pass a callback, the future completes with that callback's result.

dart
1Future<void> main() async {
2  print('before');
3  await Future.delayed(Duration(milliseconds: 300));
4  print('after');
5}

This does not block the isolate. The event loop is free to process other events while the timer is waiting.

You can also return a value:

dart
1Future<int> loadNumber() {
2  return Future.delayed(
3    Duration(milliseconds: 200),
4    () => 42,
5  );
6}

The future completes with 42 after the delay.

Duration.zero Is Still Asynchronous

A common misunderstanding is that Future.delayed(Duration.zero, ...) runs immediately. It does not. It schedules the callback for a later event-loop turn.

dart
1void main() {
2  print('sync start');
3
4  Future.delayed(Duration.zero, () {
5    print('delayed');
6  });
7
8  print('sync end');
9}

Output order is:

text
sync start
sync end
delayed

So Duration.zero means "schedule soon on the event queue," not "run inline."

Difference from Microtasks

Dart has both the event queue and the microtask queue. Microtasks run before the next event is processed. Future.delayed uses the event queue because it is timer-based.

dart
1import 'dart:async';
2
3void main() {
4  scheduleMicrotask(() => print('microtask'));
5  Future.delayed(Duration.zero, () => print('delayed event'));
6  print('sync');
7}

Typical order:

text
sync
microtask
delayed event

This matters in Flutter because microtasks can run before the next frame-related event, while delayed futures wait for the event queue turn.

await and Control Flow

When you await a delayed future, only the current async function pauses.

dart
1Future<void> worker() async {
2  print('worker start');
3  await Future.delayed(Duration(seconds: 1));
4  print('worker resume');
5}
6
7void main() {
8  worker();
9  print('main continues');
10}

The call to worker() starts execution, reaches the await, and returns control to the event loop. main continues immediately.

That is why Future.delayed is good for timers, retries, and staged UI work, but not for blocking behavior.

Common Flutter Usage

In Flutter, Future.delayed is often used for:

  • splash screen transitions
  • debounced actions
  • temporary banners
  • retry backoff

Simple example:

dart
1Future<void> showTemporaryMessage() async {
2  print('show snackbar');
3  await Future.delayed(Duration(seconds: 2));
4  print('hide snackbar');
5}

Be careful when delayed callbacks capture a widget state object. If the widget is disposed before the callback runs, updating UI can throw or produce stale behavior.

dart
1Future<void> loadLater() async {
2  await Future.delayed(Duration(seconds: 1));
3  if (!mounted) return;
4  setState(() {});
5}

That mounted check is the right habit for delayed UI work inside a State object.

Error Behavior

If the callback passed to Future.delayed throws, the future completes with an error.

dart
1Future<void> main() async {
2  try {
3    await Future.delayed(
4      Duration(milliseconds: 100),
5      () => throw StateError('boom'),
6    );
7  } catch (e) {
8    print(e);
9  }
10}

This is normal future behavior. You handle it with await and try/catch, or with .catchError.

When Not to Use It

Do not use Future.delayed as a substitute for proper lifecycle or scheduling APIs. In Flutter, if the real goal is "after the first frame," a post-frame callback is often more accurate than an arbitrary delay.

Likewise, avoid magic delays to "wait for state to settle." If correctness depends on exact state transitions, use explicit signals, not guessed timings.

Common Pitfalls

  • Assuming Duration.zero means synchronous execution.
  • Confusing event-queue delayed futures with microtasks.
  • Updating Flutter state after a delayed callback without checking mounted.
  • Using arbitrary delays instead of lifecycle-aware APIs.
  • Expecting await Future.delayed(...) to block the whole program.

Summary

  • 'Future.delayed completes a future after a timer fires.'
  • Even Duration.zero runs asynchronously on the event queue.
  • It is different from microtasks, which run earlier.
  • 'await pauses only the current async function, not the isolate.'
  • In Flutter, delayed UI callbacks should respect widget lifecycle.

Course illustration
Course illustration

All Rights Reserved.