Lesson 3 of 77 intermediate

Futures, async/await & the Event Loop

The #1 Most-Asked Dart Interview Topic

Open interactive version (quiz + challenge)

Real-world analogy

Imagine ordering food at a restaurant. You place the order (create a Future), then chat with friends (event loop continues). When the food arrives (Future completes), you eat it (then/await). You don't stand at the kitchen door blocking everyone. That's async programming — do other work while waiting.

What is it?

Futures and async/await are Dart's core async primitives. A Future represents a value that will arrive later. async/await makes async code readable. The event loop processes tasks in a single thread using microtask and event queues. Understanding this system is tested in 80%+ of Flutter interviews.

Real-world relevance

In a claims processing app, when a user submits a refund request, the app: 1) Shows a loading spinner (Future starts), 2) Sends the API request (await), 3) Updates the UI with the result (Future completes). Meanwhile, the user can still scroll and interact because the event loop keeps the UI responsive.

Key points

Code example

// Futures & Event Loop — Interview Must-Know

// Basic Future
Future<String> fetchUser() async {
  // Simulates API call — pauses this function, not the app
  await Future.delayed(Duration(seconds: 2));
  return 'John Doe';
}

// Using async/await
Future<void> loadData() async {
  try {
    final user = await fetchUser();
    print('Got: $user');
  } catch (e) {
    print('Error: $e');
  }
}

// Using .then() chain
fetchUser()
    .then((user) => print('Got: $user'))
    .catchError((e) => print('Error: $e'));

// Parallel execution with Future.wait
Future<void> loadDashboard() async {
  final results = await Future.wait([
    fetchUser(),
    fetchSettings(),
    fetchNotifications(),
  ]);
  // All three complete in parallel, not sequentially!
  final user = results[0];
  final settings = results[1];
  final notifications = results[2];
}

// EVENT LOOP ORDER — Interview Classic
void main() {
  print('1 - Sync');                              // Runs 1st

  Future.microtask(() => print('2 - Microtask')); // Runs 3rd

  Future(() => print('3 - Event queue'));          // Runs 4th

  Future.delayed(
    Duration.zero,
    () => print('4 - Delayed zero'),              // Runs 5th
  );

  print('5 - Sync again');                         // Runs 2nd
}
// Output: 1, 5, 2, 3, 4

// Completer — manual Future control
import 'dart:async';

Future<String> wrapCallback() {
  final completer = Completer<String>();
  someCallbackApi(
    onSuccess: (data) => completer.complete(data),
    onError: (e) => completer.completeError(e),
  );
  return completer.future;
}

// WRONG: await in a loop (sequential!)
for (var id in ids) {
  await fetchItem(id); // Each waits for previous — slow!
}

// RIGHT: parallel execution
final items = await Future.wait(ids.map(fetchItem));

Line-by-line walkthrough

  1. 1. Declaring an async function that returns Future
  2. 2. await pauses this function for 2 seconds, simulating an API call
  3. 3. Returns the string — automatically wrapped in a completed Future
  4. 4. The calling function is also async
  5. 5. try/catch handles both sync and async errors with await
  6. 6. awaiting the result — this line pauses until fetchUser completes
  7. 7. catch handles any error that fetchUser might throw
  8. 8. Alternative .then() syntax — same behavior, different style
  9. 9. catchError handles errors in the .then() chain
  10. 10. Future.wait takes a list of Futures and runs them ALL in parallel
  11. 11. All three API calls happen simultaneously, not one after another
  12. 12. Destructuring the results list
  13. 13. The classic event loop question — sync code runs first
  14. 14. Microtask queue drains before the event queue
  15. 15. Event queue processes after all microtasks

Spot the bug

Future<void> loadData() async {
  final data = fetchData();
  print(data.length);
}

Future<List<int>> fetchData() async {
  await Future.delayed(Duration(seconds: 1));
  return [1, 2, 3];
}
Need a hint?
What type does fetchData() return if you don't await it?
Show answer
Missing await! 'final data = fetchData()' assigns a Future<List<int>>, not a List<int>. Calling .length on a Future is a compile error (or if dynamic, a runtime crash). Fix: 'final data = await fetchData();'

Explain like I'm 5

Imagine you're at a pizza shop. You order a pizza (that's a Future — a promise of pizza). While it bakes, you go sit down and play on your phone (the event loop doing other work). When the pizza is ready, the counter calls your name (the Future completes). You go grab it (await gets the value). You didn't just stand at the counter staring — you did other stuff while waiting!

Fun fact

Dart's event loop is inspired by JavaScript's event loop, but Dart added a separate microtask queue that drains completely before the next event. This is why Future.microtask() always runs before Future() — a common interview trick question.

Hands-on challenge

Write a function fetchAllUsers() that takes a list of user IDs and fetches all user profiles in parallel using Future.wait. Include proper error handling with try/catch. If any single fetch fails, return the successfully fetched users and log the failures.

More resources

Open interactive version (quiz + challenge) ← Back to course: Flutter Interview Mastery