What is the difference between async/await and Future?
4 min read
Dart Fundamentals
Future<T> and async/await are not alternatives — they work together. Future<T> is the object (a promise of a value that arrives later); async/await is the syntax sugar that lets you read async code top-to-bottom as if it were synchronous. Every async function returns a Future under the hood.
| Concept | What it is | Mental model |
|---|---|---|
Future<T> | The return type — an object that will eventually hold a T or an error | A receipt you can redeem later |
async | Marks a function so it can use await and auto-wraps its return in a Future | "This function does async work" |
await | Pauses the current async function until the Future completes, then unwraps the value | "Wait here, then continue" |
Important: await doesn't block the thread. Dart is single-threaded; it yields control back to the event loop so the UI keeps responding.
Code in action
// The Future is the object you receive
Future<String> fetchUserName() {
return Future.delayed(const Duration(seconds: 2), () => 'Alice');
}
// Style A: .then() — chained callbacks
void mainCallback() {
fetchUserName()
.then((name) => print('Hello, $name'))
.catchError((e) => print('Error: $e'));
}
// Style B: async/await — same logic, reads top-to-bottom
Future<void> mainAwait() async {
try {
final name = await fetchUserName(); // pauses here, doesn't block UI
print('Hello, $name');
} catch (e) {
print('Error: $e');
}
}
Both do the exact same thing. async/await is just sugar over .then().
Sequential vs parallel — the perf trap
// 🐢 Sequential — 5s total
Future<void> slow() async {
final user = await fetchUser(); // 2s
final posts = await fetchPosts(); // 3s, only starts AFTER user finishes
}
// 🚀 Parallel — 3s total (they run together)
Future<void> fast() async {
final results = await Future.wait([fetchUser(), fetchPosts()]);
final user = results[0], posts = results[1];
}
// ✨ Even cleaner with Dart 3 records
Future<void> fastest() async {
final (user, posts) = await (fetchUser(), fetchPosts()).wait;
}
Rule: if two awaits don't depend on each other, you're leaving speed on the table.
When to use which style
| Situation | Pick |
|---|---|
| Reading/writing a sequence of async steps | async/await |
Firing off async work from a non-async context (constructors, initState, event handlers) | Return the Future or use .then() |
| Running independent async work in parallel | Future.wait / record .wait |
| Need a fallback if a Future is slow | .timeout(...) |
| Want whichever finishes first | Future.any([...]) |
Common mistakes to avoid
// ❌ Forgetting await — fire and forget, errors get swallowed
Future<void> save() async {
saveToDb(); // ⚠️ returns a Future nobody is watching
print('Saved!'); // prints before save actually happens
}
// ✅ Await it (or explicitly mark as intentional with `unawaited(...)`)
Future<void> saveOk() async {
await saveToDb();
print('Saved!');
}
// ❌ Awaiting things that could run in parallel
final a = await fetchA();
final b = await fetchB(); // wasted seconds if b doesn't depend on a
// ✅ Parallelise
final (a, b) = await (fetchA(), fetchB()).wait;
// ❌ Thinking async = multi-threaded
// Dart runs on a single thread. async/await yields to the event loop,
// it does NOT run on a background thread. For CPU-heavy work use Isolate.
// ❌ Marking a function async with no await — pointless wrapping
Future<int> answer() async => 42; // wraps 42 in an unnecessary Future
int answerSync() => 42; // do this instead
Interview follow-ups
-
What happens if you forget
await? The Future runs in the background, but you lose the result, can't catch errors (they become uncaught async errors), and your code continues past it immediately. Most teams enable theunawaited_futureslint to catch this. -
Does
asyncrun code on a separate thread? No. Dart is single-threaded with an event loop.awaityields to the loop, letting other events (UI frames, taps, timers) run, but the work itself is on the same isolate. For real parallelism / CPU-heavy work, useIsolate.run(...)orcompute(...). -
What's the difference between returning a Future from a sync function vs an async function?
Future<T> foo() => bar();returns whateverbar()returns directly.Future<T> foo() async => await bar();wraps the result in a new Future and adds a microtask hop. The sync version is slightly faster; the async version gives youtry/catchandawaitinside. -
When would you ever still use
.then()overawait? When you're not in anasyncfunction and can't make it one — constructors,initState, top-level callbacks. Also handy for fire-and-forget logging where you genuinely don't want to wait.
How helpful was this content?
Please sign in to rate this article.