skip to content

Futures & Await

Future<T> with async and await, then/catchError chains versus try/catch, and Future.wait, Future.any and timeout for combining work. Interviewers probe what an async function returns.

part ofDartoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In Dart, what does a function marked `async` return to its caller, and what does `await` do inside it?

level: juniorimportance: must knowfreq 82%

answer

  1. always a future, never the raw value
  2. return value completes it
  3. throw completes it with an error
  4. synchronous until the first await
  5. await suspends, then resumes or throws

basics

~20 s

An async Dart function always returns a Future<T> immediately; returning a value completes it and throwing completes it with an error. Inside, await suspends the function until a future completes, then resumes with its value or rethrows its error.

solid answer

~40 s

Marking a body `async` makes the function return a `Future<T>` no matter what the body does: `return 42;` in a `Future<int>` function completes that future with `42`, and a `throw` completes it with an error rather than throwing out of the call. The body runs **synchronously until its first `await`**; at that point the function suspends, the caller gets the pending future and carries on, and the function resumes later when the awaited future completes. `await` evaluates to the completed value, or rethrows the error so an ordinary `try`/`catch` around it works. A function with nothing to return should be declared `Future<void>` so callers can still await it, and `await` is only allowed inside an `async` body.

code

dart · 14 lines
dart
Future<String> greet(String name) async {
  if (name.isEmpty) throw ArgumentError('name is empty');
  await Future.delayed(const Duration(milliseconds: 100));
  return 'Hello, $name';
}

Future<void> main() async {
  print(await greet('Dash')); // Hello, Dash
  try {
    await greet('');
  } on ArgumentError catch (e) {
    print('caught: ${e.message}'); // caught: name is empty
  }
}

go deeper

for a junior

Say plainly that an async function returns a Future, that return completes it and throw fails it, and that await gives you the value or rethrows the error.

for a middle

Explain that the body runs synchronously until the first await, what the caller receives at that point, and why Future<void> beats void for async functions.

for a senior

Diagnose bugs where a Future leaked through as a value, a try without await caught nothing, or code before the first await ran earlier than expected.

for a principal

Set conventions for async APIs: Future<void> over void, no needless async wrappers, and lints that keep futures from silently escaping.

## What `async` changes about a function A `Future<T>` in Dart is an object representing a value of type `T` (or an error) that will be available later. Adding `async` before a function body changes the function's contract in three ways: 1. **The return value is always a future.** The declared type is `Future<T>`, and the caller receives that future immediately, before the work finishes. 2. **`return` completes the future.** Inside the body you return a plain `T`; Dart wraps it. Returning a `Future<T>` also works: the result follows that future. 3. **`throw` completes the future with an error.** An exception inside an `async` body never escapes the call synchronously, even if it happens before the first `await`. The caller sees it only when it awaits the future or attaches an error handler. ```dart Future<int> loadScore() async { final raw = await fetchRaw(); // suspends here if (raw.isEmpty) throw FormatException('empty'); return int.parse(raw); // completes the future } ``` ## When the body actually runs The dart.dev async guide states the rule precisely: **an `async` function runs synchronously until the first `await`**. Everything before that first `await` executes during the call itself, on the caller's stack. When execution reaches `await`, the function **suspends**: it hands the still-pending future back to the caller, and the caller continues with its next statement. Later, when the awaited future completes, the rest of the body resumes. ```dart Future<void> f() async { print('A'); await null; print('C'); } void main() { f(); print('B'); } // prints A, B, C ``` Nothing here involves another thread. A Dart isolate runs one piece of code at a time; `await` simply lets other pending work run while this function waits. ## What `await` evaluates to - If the awaited future completes with a **value**, the `await` expression evaluates to that value, fully typed: `await Future<int>` is an `int`. - If it completes with an **error**, `await` **rethrows** that error at that line, with its stack trace, so `try`/`catch`/`finally` around awaits behaves just like synchronous code. - Awaiting a non-future value is allowed but pointless; the `await_only_futures` lint (core set) flags it, with `await null` accepted as a deliberate yield. - `await` is legal only inside a function body marked `async` (or `async*`). ## Return types and common mistakes | Declaration | What the caller gets | Verdict | |---|---|---| | `Future<int> load() async` | a future completing with an `int` | normal | | `Future<void> save() async` | a future to await for completion and errors | normal for no-value work | | `void save() async` | nothing it can await | fire-and-forget only; `avoid_void_async` flags it | | `int load() async` | compile error | an `async` body cannot return a bare `int` type | Mistakes interviewers listen for: - **Forgetting `await`**: `final score = loadScore();` makes `score` a `Future<int>`, not an `int`. The analyzer usually catches it when the variable is used as an `int`, but not when it is only printed or passed along as an `Object`. - **Expecting a synchronous throw**: wrapping `loadScore()` in `try` without `await` catches nothing, because the error lands in the future. - **Adding `async` for no reason**: Effective Dart says not to use `async` when it has no useful effect, such as a function that only returns another future. ## `async` without `await` An `async` body that never awaits is legal, and sometimes it is the shortest correct code. Effective Dart lists the cases where `async` still earns its place: - **Returning an error asynchronously**: `async` plus `throw` is shorter than `return Future.error(...)`. - **Wrapping a plain value**: `Future<String> label() async => 'ready';` is shorter than `Future.value('ready')`. - **Using `await`**, the obvious case. The opposite advice also holds: if removing `async` does not change behaviour, for example a function that only returns another future, such as `return Future.any([left, right]);`, leave it off. ## Why this matters in practice Knowing exactly what an `async` function returns explains most day-to-day async bugs: a value that is still a `Future`, an error that was never awaited, a statement that ran earlier than expected because everything before the first `await` is synchronous. It is the foundation for combining futures with `Future.wait`, adding a `timeout`, or deciding when a future may deliberately go unawaited.

  • Why should an async function with no result return `Future<void>` rather than `void`?
    A `Future<void>` lets callers await completion and receive errors; a `void` async function gives them nothing to wait on, so its failures surface only as unhandled errors and ordering is lost. Effective Dart asks for `Future<void>`, and the opt-in `avoid_void_async` lint flags `void f() async`, with an exception for a top-level `main`.
  • If an async function throws before its first await, does the caller's try/catch around the call catch it?
    Only if the caller awaits. The exception completes the returned future with an error instead of propagating synchronously, so `try { load(); } catch (_) {}` without `await` catches nothing, while `try { await load(); } catch (_) {}` does.

saying these in an interview costs you the question

  • An async function returns its value directly once it finishes
  • await blocks the thread until the future completes
  • Code before the first await runs later, on the event loop
  • An exception before the first await is thrown synchronously to the caller
  • void is the right return type for an async function with no result
open as a page

In Dart, a dashboard loads profile, feed and stats from three independent futures; how do you run them concurrently and collect typed results?

level: middleimportance: must knowfreq 68%

basics

~20 s

Start all three futures before awaiting any: Future.wait([a, b, c]) gives a List in argument order, and Dart 3's (a, b, c).wait gives a typed record. Awaiting one after another serialises the calls and sums their latencies.

open as a page

In Dart, how do `then`, `catchError` and `whenComplete` chains map onto `await` with try/catch/finally, and which should you prefer?

level: middleimportance: should knowfreq 48%

basics

~10 s

then runs code with the value, catchError handles errors like catch, and whenComplete runs cleanup like finally. The same flow written with await inside try/catch/finally is clearer, and Effective Dart prefers it.

open as a page

In Dart, what do `Future.timeout` and `Future.any` actually do to the futures they give up on, and why doesn't either stop the slow work?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Future.timeout returns a new future that fails with TimeoutException, or uses onTimeout, if the source is late; Future.any completes with the first result, value or error. Neither cancels anything: the losing work keeps running and its result is discarded.

open as a page

In Dart, what goes wrong when a future is neither awaited nor handled, and when should you reach for `unawaited()` or `ignore()` instead?

level: seniorimportance: should knowfreq 38%

basics

~20 s

An unawaited future lets the next statement run before its work finishes, and its error becomes unhandled. Use unawaited(f) from dart:async to mark deliberate fire-and-forget while still surfacing errors, and f.ignore() only when even its errors do not matter.

open as a page

In Dart, what is `FutureOr<T>`, and why does Effective Dart accept it in parameters and callbacks but not as a return type?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

FutureOr<T> is Dart's union of T and Future<T>: a value that is either a plain T or a future of one. Accept it in parameters and callback return types, but return Future<T>, so callers never check which they got.

open as a page