In Dart, what goes wrong when a future is neither awaited nor handled, and when should you reach for `unawaited()` or `ignore()` instead?
answer
- next line runs before it finishes
- its error becomes unhandled
- unawaited_futures and discarded_futures lints
- unawaited(): intent only, errors still reported
- ignore(): drops value and error
basics
~20 sAn 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.
solid answer
~40 sForgetting `await` causes two bugs. **Ordering**: the next statement runs before the work finishes, so a save may still be pending when the screen closes or a test ends. **Errors**: nobody listens to the future, so a failure is reported as an unhandled error instead of reaching your `catch`. The opt-in `unawaited_futures` lint flags `Future`-typed expressions left unawaited inside `async` bodies, and `discarded_futures` flags async calls from non-`async` functions. When fire-and-forget is intended, wrap the call in `unawaited(...)` from `dart:async`: it does nothing at run time except say so, and an error is still reported. If the result and even its errors are genuinely irrelevant, call `future.ignore()`, which handles and drops both.
go deeper
Remember that leaving out await lets the next line run early and hides the future's error from your try/catch.
Explain unhandled future errors, the unawaited_futures and discarded_futures lints, and the run-time difference between unawaited() and ignore().
Hunt flaky tests and lost writes caused by unawaited futures, enable the opt-in lints, and choose await, unawaited, ignore or a handler deliberately.
Make forgotten futures a lint-enforced class of bug across the codebase, with a written rule for when fire-and-forget is allowed.
## Two bugs from one missing `await` Calling an `async` function starts its work and returns a `Future`. If the caller neither awaits that future nor attaches a handler, two things go wrong. **1. Ordering.** The caller carries on immediately. Code such as ```dart Future<void> onSavePressed() async { saveDraft(draft); // missing await closeEditor(); // runs while the save is still pending } ``` closes the editor before the draft is written. In tests, the test body can finish, and teardown can run, while the work is still in flight. **2. Errors.** A future that completes with an error and has no listener reports that error as **unhandled**. The SDK notes that futures do not delay error reporting until a listener is added: if the first `then` or `catchError` is attached after the error, it is still reported as unhandled. The failure never reaches the `try`/`catch` you wrote around the call, because nothing awaited it. ## Lints that catch it | Lint | Flags | In a default set? | |---|---|---| | `unawaited_futures` | a `Future` or `FutureOr` expression left unawaited inside an `async` body | no, opt-in | | `discarded_futures` | calling an async function from a non-`async` function and dropping the future | no, opt-in | | `avoid_void_async` | `void f() async`, which hides the future from callers | no, opt-in | | `await_only_futures` | `await` on something that is not a future | yes, core | Because the first three are opt-in, a team has to enable them in `analysis_options.yaml`; the recommended sets alone will not warn about a forgotten `await`. ## `unawaited()` versus `ignore()` Sometimes not waiting is correct: prefetching images the next screen may need, warming a cache, writing a log line. Dart gives two explicit tools, with different error behaviour: - **`unawaited(future)`** is a top-level function in `dart:async` (since Dart 2.15). Its body is empty; the only effect is that the expression no longer has type `Future`, which satisfies `unawaited_futures`. The SDK is explicit that an error from that future **is still reported as unhandled**, so use it for futures *expected* to succeed. - **`future.ignore()`** is an extension method on `Future` (since Dart 2.14). It attaches handlers for both value and error and discards them, so even a failure is silent. Use it only when the result is truly irrelevant, such as a response the user no longer needs. ```dart import 'dart:async'; Future<void> openDashboard() async { unawaited(prefetchAvatars()); // deliberate; failures still surface staleRefresh().ignore(); // outcome irrelevant, errors dropped too await loadVisibleTiles(); } ``` ## Choosing deliberately 1. **Default**: `await` it. Most futures matter for ordering or errors. 2. **Must not block, but failures matter**: `unawaited(...)`, and make sure something upstream reports unhandled errors. 3. **Must not block, failures irrelevant**: `.ignore()`. 4. **Must not block, and you want to react to failure**: attach a handler, for example `f.catchError(...)` or `f.onError<E>(...)`, and then mark it `unawaited` if the lint still applies. ## Futures started from synchronous code The `discarded_futures` lint targets a related situation: a **non-`async`** function, such as a synchronous event handler, calls an async function and drops the returned future. ```dart void onRefreshTapped() { reloadDashboard(); // Future discarded: flagged by discarded_futures } ``` There are three honest fixes: make the caller `async` and await the call if its own caller can wait; keep it synchronous and write `unawaited(reloadDashboard())` to record that fire-and-forget is intended; or attach an error handler before marking it unawaited, so failures are shown to the user rather than only reported as unhandled. Where unhandled errors end up is decided by the current zone's error handling, a separate subject. ## Why interviewers ask Forgotten futures are among the most common async defects in Dart and Flutter codebases: flaky tests, lost writes, and error reports that point nowhere. A strong answer names both failure modes, the opt-in lints, and the difference between marking intent (`unawaited`) and discarding outcomes (`ignore`).
- Why is `// ignore: unawaited_futures` weaker than calling `unawaited(...)`?Both silence the lint, and the lint documentation accepts either. But `unawaited(...)` expresses the intent in code, survives refactors that move the line, and is searchable, while an ignore comment only suppresses a diagnostic on one line. Neither handles errors, so both need a reason to believe the future will succeed.
- Does calling `unawaited(f)` prevent `f`'s error from being reported?No. `unawaited` does nothing at run time; it only changes the static type of the expression so the lint is satisfied. If `f` fails and nothing else handles it, the error is still reported as unhandled. Use `f.ignore()` when you want errors discarded too.
saying these in an interview costs you the question
- unawaited() catches and discards the future's errors
- The recommended lint set already warns about forgotten awaits
- A missing await only delays the result, never loses an error
- ignore() and unawaited() behave the same at run time
- Attaching catchError later still handles an error that already happened