Dart
Future
Synchronous
Programming
Tutorial

How to wait for a Dart Future to complete, synchronously

Master System Design with Codemia

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

Introduction

In normal Dart code, you do not block the current isolate until a Future finishes. Dart is built around an event loop, so the correct answer is usually to restructure the calling code to use await or to finish the asynchronous work earlier and expose cached state later.

Why Synchronous Waiting Is a Bad Fit for Dart

A Future represents work that completes later, often after I/O, timers, or another event-loop turn. If you tried to block the isolate while waiting, you would stop the same runtime that needs to deliver the completion event. In Flutter, that would also freeze rendering and user interaction.

That is why idiomatic Dart code looks synchronous but is still non-blocking:

dart
1Future<String> fetchUserName() async {
2  await Future.delayed(const Duration(milliseconds: 300));
3  return 'Ava';
4}
5
6Future<void> main() async {
7  final name = await fetchUserName();
8  print(name);
9}

The await keyword pauses only the async function, not the whole isolate.

Push async Up the Call Chain

When developers ask for synchronous waiting, the real problem is often that a higher-level function still expects a plain return value. The fix is usually to make that caller asynchronous too.

dart
1Future<int> loadCount() async {
2  await Future.delayed(const Duration(milliseconds: 200));
3  return 42;
4}
5
6Future<void> showCount() async {
7  final count = await loadCount();
8  print('count: $count');
9}

This propagation is normal in Dart. Once one layer becomes asynchronous, callers above it often need to become asynchronous as well.

In Flutter, the same rule applies to UI logic:

dart
1Future<void> refreshData() async {
2  final value = await loadCount();
3  setState(() {
4    _count = value;
5  });
6}

That keeps the UI responsive while the work completes.

When an API Really Needs a Synchronous Value

If an API must return immediately, you cannot magically convert unfinished async work into a synchronous result on the same isolate. You need a different design:

  • start initialization earlier
  • cache the result
  • change the API to return Future
  • move CPU-heavy work to another isolate

A common pattern is eager initialization followed by synchronous reads:

dart
1class SettingsStore {
2  String? _theme;
3
4  Future<void> initialize() async {
5    await Future.delayed(const Duration(milliseconds: 200));
6    _theme = 'dark';
7  }
8
9  String get theme {
10    if (_theme == null) {
11      throw StateError('SettingsStore is not initialized');
12    }
13    return _theme!;
14  }
15}

Here, the asynchronous work happens during startup. Later access is synchronous because the data is already in memory.

Coordinating Several Futures

Sometimes the real requirement is not synchronous waiting, but "do not continue until several tasks are done." In that case, use Future.wait():

dart
1Future<void> loadEverything() async {
2  final results = await Future.wait([
3    Future.value('profile'),
4    Future.value('settings'),
5    Future.value('messages'),
6  ]);
7
8  print(results);
9}

This still respects Dart's asynchronous model while giving you a single point where the next step begins.

CPU Work Is a Different Problem

If the issue is expensive computation rather than I/O, waiting on a Future is not the core problem. Long CPU work should be moved to another isolate so the main isolate remains responsive. In Flutter, helpers such as compute() exist for exactly that reason.

That distinction matters because developers sometimes look for synchronous waiting when the real requirement is background execution.

Common Pitfalls

  • Blocking the UI isolate conceptually, which leads to frozen Flutter apps or awkward design workarounds.
  • Refusing to make callers async, then fighting Dart's type system instead of following it.
  • Trying to read asynchronously loaded state before initialization has completed.
  • Confusing I/O-bound Future work with CPU-bound computation that belongs in another isolate.
  • Searching for a hidden synchronous wait primitive when the real solution is a different API boundary.

Summary

  • Dart does not encourage synchronous waiting for Future completion on the current isolate.
  • The normal fix is to make the caller async and use await.
  • If synchronous reads are required, initialize earlier and cache the result.
  • Use Future.wait() when multiple async tasks must finish before continuing.
  • Move heavy computation to another isolate rather than trying to block the event loop.

Course illustration
Course illustration

All Rights Reserved.