Watch the video (12 min)
On this page23 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What async and await Do
  5. 2. An async Function Returns a Future
  6. 3. await Unwraps the Value
  7. 4. Future<void> main() async
  8. 5. await Pauses Only That Function
  9. 6. Rewriting a .then Chain with await
  10. 7. Returning Values from async Functions
  11. 8. await Only Inside async
  12. 9. await in a Loop Runs One at a Time
  13. 10. Forgetting await
  14. 11. A Real-World Example
  15. Code Examples
  16. Common Beginner Mistakes
  17. Best Practices
  18. Mini Challenge
  19. Practice Questions
  20. Practice Answers
  21. Quick Revision
  22. Key Takeaways
  23. What's Next?

Lecture 89 — async & await

Category15 — Asynchronous Programming
Lecture089 of 140
Estimated video length12 min
TargetDart 3.12.2
LevelIntermediate

A .then chain works, but each step hides inside its own callback. With async and await, asynchronous code reads top to bottom like ordinary code — and still never blocks the program.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.


Prerequisites

Students should already understand, from Lectures 030–038, 072 and 087–088:

  • functions, parameters, return values, and arrow functions
  • generic types such as List<int>
  • the difference between synchronous and asynchronous code
  • Future<T>, Future.delayed, and .then chains

Connection With Previous Lectures

In Lecture 088, we loaded a student and then her marks with a .then chain:

dart
Future<String> fetchStudent() {
  return Future.delayed(Duration(seconds: 1), () => 'Sara');
}

Future<int> fetchMarks(String name) {
  return Future.delayed(Duration(seconds: 1), () => 92);
}

void main() {
  fetchStudent()
      .then((name) => fetchMarks(name))
      .then((marks) => print('Marks: $marks'));
}
output
Marks: 92

It works, but try printing Sara: 92. The name lives only inside the first callback; the second callback cannot see it. Every new step adds another callback and pushes the logic further from the plain, line-by-line code we have written since Lecture 003.


Learning Objectives

By the end of this lecture you will be able to:

  • Mark a function async and explain why it always returns a Future
  • Use await to get the value out of a Future
  • Write Future<void> main() async
  • Explain why await pauses only its own function
  • Rewrite a .then chain using await
  • Return values from async functions
  • Predict the total time of await inside a loop
  • Recognize the errors and wrong output caused by a missing async or await

1. What async and await Do

Two keywords work together:

  • async marks a function as asynchronous. It goes before the function body.
  • await goes in front of a Future and means "wait here until this value is ready".

Analogy — you order tea at a café. You wait at the counter for your cup, but the café does not close while you wait: other customers keep ordering. await makes your function wait; the rest of the program keeps going.


2. An async Function Returns a Future

Mark a function async and Dart wraps whatever it returns in a Future:

dart
Future<String> greet() async {
  return 'Hello, Ali';
}

void main() {
  print(greet());
}
output
Instance of 'Future<String>'

The function returns the text 'Hello, Ali', but the caller receives a Future<String> — a promise of that text. That is why the return type is Future<String>, not String.


3. await Unwraps the Value

To get the value itself, put await in front of the call:

dart
Future<String> greet() async {
  return 'Hello, Ali';
}

Future<void> main() async {
  var message = await greet();
  print(message);
}
output
Hello, Ali

await greet() waits for the Future<String> to complete and gives back a plain String. Dart infers message as String, so you use it like any other variable.


4. Future<void> main() async

await is only allowed inside an async function, so main itself must become one. Since every async function returns a Future, and main returns nothing, its return type is Future<void>:

dart
Future<double> fetchBalance() {
  return Future.delayed(Duration(seconds: 1), () => 1500.0);
}

Future<void> main() async {
  print('Checking balance...');
  var balance = await fetchBalance();
  print('Balance: $balance');
}
output
Checking balance...
Balance: 1500.0

The first line prints at once; the second appears one second later. You will write Future<void> main() async in almost every program from now on.


5. await Pauses Only That Function

await does not freeze the whole program. Here main is not async, and it does not wait for loadMarks:

dart
Future<void> loadMarks() async {
  print('2. Loading marks...');
  await Future.delayed(Duration(seconds: 1));
  print('4. Marks: 85');
}

void main() {
  print('1. Start');
  loadMarks();
  print('3. main keeps going');
}
output
1. Start
2. Loading marks...
3. main keeps going
4. Marks: 85

loadMarks runs normally until its first await. There it pauses, and control goes back to main, which prints line 3. One second later, loadMarks resumes where it stopped. Notice await Future.delayed(...) with no value — waiting is all we need.


6. Rewriting a .then Chain with await

Now the problem from the start of the lecture. Each .then becomes one await line:

dart
Future<String> fetchStudent() {
  return Future.delayed(Duration(seconds: 1), () => 'Sara');
}

Future<int> fetchMarks(String name) {
  return Future.delayed(Duration(seconds: 1), () => 92);
}

Future<void> main() async {
  var name = await fetchStudent();
  var marks = await fetchMarks(name);
  print('$name: $marks');
}
output
Sara: 92

The callbacks are gone. name and marks are ordinary variables in one function, so the last line can use both. Under the hood it is still the same Futures — await is simply a clearer way to write them.


7. Returning Values from async Functions

Inside an async function you return the plain value. Dart delivers it through the Future:

dart
Future<int> addMarks(int a, int b) async {
  await Future.delayed(Duration(milliseconds: 500));
  return a + b;
}

Future<void> main() async {
  var total = await addMarks(80, 75);
  print('Total: $total');
}
output
Total: 155

return a + b returns an int; the caller gets a Future<int> and awaits it. The return type must be a Future. Write int instead and Dart refuses:

dart
int addMarks(int a, int b) async {
  return a + b;
}
output
Error: Functions marked 'async' must have a return type assignable to 'Future'.
int addMarks(int a, int b) async {
^^^

8. await Only Inside async

Forget async on the function and await is not allowed:

dart
Future<int> fetchScore() async {
  return 40;
}

void main() {
  var score = await fetchScore();
  print(score);
}
output
Error: 'await' can only be used in 'async' or 'async*' methods.
  var score = await fetchScore();
              ^^^^^

The fix is Future<void> main() async. The async* in the message is a different kind of function — we will study it in Lecture 096 — Async Generators.


9. await in a Loop Runs One at a Time

await inside a loop waits for each Future before the next one starts. Dart's built-in Stopwatch measures how long that takes (..start() is the cascade from Lecture 020):

dart
Future<int> fetchPrice(String item) {
  return Future.delayed(Duration(seconds: 1), () => item.length * 100);
}

Future<void> main() async {
  var watch = Stopwatch()..start();
  var items = ['pen', 'book', 'bag'];
  var total = 0;
  for (var item in items) {
    var price = await fetchPrice(item);
    print('$item: $price');
    total += price;
  }
  print('Total: $total');
  print('Took about ${watch.elapsed.inSeconds} seconds');
}
output
pen: 300
book: 400
bag: 300
Total: 1000
Took about 3 seconds

Three one-second waits, one after another: three seconds. That is right when each step needs the previous result. When the prices are independent, they could all load at once. We will learn to run Futures in parallel in Lecture 091 — Working with Multiple Futures.


10. Forgetting await

Leave out await and nothing crashes — you just get the wrong thing:

dart
Future<String> fetchName() async {
  return 'Zayan';
}

Future<void> main() async {
  var name = fetchName();
  print('Hello, $name');
}
output
Hello, Instance of 'Future<String>'

name is the Future, not the text. With a Future<void>, the mistake is about order:

dart
Future<void> saveScore(int score) async {
  await Future.delayed(Duration(seconds: 1));
  print('Saved $score');
}

Future<void> main() async {
  saveScore(90);
  print('Done');
}
output
Done
Saved 90

main printed Done before the score was saved. Dart's analyzer can catch this. Add the unawaited_futures rule to the project's analysis_options.yaml (Lecture 002):

yaml
linter:
  rules:
    - unawaited_futures

Then dart analyze reports:

output
info - bin/main.dart:7:3 - Missing an 'await' for the 'Future' computed by this expression. Try adding an 'await' or wrapping the expression with 'unawaited'. - unawaited_futures

Write await saveScore(90); and Saved 90 prints first.


11. A Real-World Example

Sara orders shoes. The shop looks up the price and the stock, then applies a 10% discount:

dart
Future<int> fetchPrice(String item) {
  return Future.delayed(Duration(milliseconds: 500), () => 1200);
}

Future<int> fetchStock(String item) {
  return Future.delayed(Duration(milliseconds: 500), () => 3);
}

Future<double> placeOrder(String customer, String item, int quantity) async {
  print('Placing order for $customer...');
  var price = await fetchPrice(item);
  var stock = await fetchStock(item);
  if (quantity > stock) {
    print('Only $stock left.');
    return 0;
  }
  var total = price * quantity * 0.9;
  print('$quantity x $item at $price, 10% off');
  return total;
}

Future<void> main() async {
  var total = await placeOrder('Sara', 'Shoes', 2);
  print('Total: $total');
  var second = await placeOrder('Ali', 'Shoes', 5);
  print('Total: $second');
}
output
Placing order for Sara...
2 x Shoes at 1200, 10% off
Total: 2160.0
Placing order for Ali...
Only 3 left.
Total: 0.0

placeOrder awaits two Futures, makes an ordinary if decision, and returns a double — which its callers receive as a Future<double> and await.


Code Examples

Example 1 — async Returns a Future

dart
Future<int> fetchAge() async {
  return 20;
}

Future<void> main() async {
  print(fetchAge());
  print(await fetchAge());
}
output
Instance of 'Future<int>'
20

Example 2 — Awaiting a Delayed Value

dart
Future<double> fetchTemperature() {
  return Future.delayed(Duration(seconds: 1), () => 31.5);
}

Future<void> main() async {
  print('Reading sensor...');
  var temperature = await fetchTemperature();
  print('Temperature: $temperature');
}
output
Reading sensor...
Temperature: 31.5

Example 3 — Only the async Function Pauses

dart
Future<void> download() async {
  print('Download started');
  await Future.delayed(Duration(seconds: 1));
  print('Download finished');
}

void main() {
  download();
  print('Zayan keeps playing');
}
output
Download started
Zayan keeps playing
Download finished

Example 4 — Same Result, Two Styles

dart
Future<String> fetchCity() {
  return Future.delayed(Duration(milliseconds: 500), () => 'Lahore');
}

Future<void> withThen() {
  return fetchCity().then((city) => print('With then: $city'));
}

Future<void> withAwait() async {
  var city = await fetchCity();
  print('With await: $city');
}

Future<void> main() async {
  await withThen();
  await withAwait();
}
output
With then: Lahore
With await: Lahore

Example 5 — Returning a Value After Waiting

dart
Future<String> grade(int marks) async {
  await Future.delayed(Duration(milliseconds: 500));
  return marks >= 50 ? 'Pass' : 'Fail';
}

Future<void> main() async {
  print('Ali: ${await grade(72)}');
  print('Sara: ${await grade(45)}');
}
output
Ali: Pass
Sara: Fail

await works inside ${} too — it is an expression like any other.

Example 6 — Awaiting a Future<void>

dart
Future<void> saveScore(int score) async {
  await Future.delayed(Duration(seconds: 1));
  print('Saved $score');
}

Future<void> main() async {
  await saveScore(90);
  print('Done');
}
output
Saved 90
Done

Example 7 — Student Marks

dart
Future<int> fetchMark(String subject) {
  var marks = {'Math': 80, 'Science': 75, 'English': 85};
  return Future.delayed(Duration(milliseconds: 300), () => marks[subject]!);
}

Future<void> main() async {
  var math = await fetchMark('Math');
  var science = await fetchMark('Science');
  var english = await fetchMark('English');
  print('Student: Ali');
  print('Total: ${math + science + english}');
}
output
Student: Ali
Total: 240

Example 8 — Shopping Total

dart
Future<int> fetchPrice(String item) {
  var prices = {'Notebook': 500, 'Pen': 50, 'Bag': 1500};
  return Future.delayed(Duration(milliseconds: 300), () => prices[item]!);
}

Future<void> main() async {
  var cart = ['Notebook', 'Pen', 'Bag'];
  var total = 0;
  for (var item in cart) {
    var price = await fetchPrice(item);
    print('$item: $price');
    total += price;
  }
  print('Total: $total');
}
output
Notebook: 500
Pen: 50
Bag: 1500
Total: 2050

Example 9 — Bank Balance

dart
Future<double> deposit(double balance, double amount) async {
  await Future.delayed(Duration(milliseconds: 300));
  return balance + amount;
}

Future<double> withdraw(double balance, double amount) async {
  await Future.delayed(Duration(milliseconds: 300));
  return balance - amount;
}

Future<void> main() async {
  var balance = 1000.0;
  balance = await deposit(balance, 250.5);
  balance = await withdraw(balance, 100);
  print('Balance: $balance');
}
output
Balance: 1150.5

The withdrawal must wait for the deposit's result, so awaiting one after the other is exactly right here.

Example 10 — Temperature Conversion

dart
Future<double> fetchCelsius(String city) {
  return Future.delayed(Duration(milliseconds: 500), () => 25.0);
}

Future<double> toFahrenheit(String city) async {
  var celsius = await fetchCelsius(city);
  return celsius * 9 / 5 + 32;
}

Future<void> main() async {
  var fahrenheit = await toFahrenheit('Karachi');
  print('Karachi: $fahrenheit °F');
}
output
Karachi: 77.0 °F

One async function awaits another, and main awaits the result.


Common Beginner Mistakes

1. Printing the Future instead of its value

dart
Future<void> main() async {
  // var name = fetchName(); // ✗ name is a Future<String>
  var name = await fetchName();
  print('Hello, $name');
}

The wrong version prints Hello, Instance of 'Future<String>'.

2. Using await in a function that is not async

dart
void main() {
  // var score = await fetchScore(); // ✗
}
output
Error: 'await' can only be used in 'async' or 'async*' methods.

Write Future<void> main() async.

3. Giving an async function a non-Future return type

dart
// int addMarks(int a, int b) async { return a + b; } // ✗
output
Error: Functions marked 'async' must have a return type assignable to 'Future'.

Write Future<int>, and still return the plain int.

4. Calling a Future<void> function without await

dart
Future<void> main() async {
  // saveScore(90); // ✗ "Done" prints first
  await saveScore(90);
  print('Done');
}

No error appears — only the wrong order. Turn on unawaited_futures to catch it.

5. Thinking await freezes the whole program

await pauses only the function it is written in. Callers that do not await it keep running, as Step 5 showed.


Best Practices

  • Prefer async/await over .then chains — the code reads in the order it runs.
  • Use Future<void> main() async whenever main needs to wait for something.
  • Write the full return type: Future<int>, Future<String>, Future<void>.
  • Await every Future whose result or completion you depend on.
  • Enable the unawaited_futures lint so a forgotten await is reported.
  • Await in a loop only when each step needs the previous one; otherwise run them in parallel (Lecture 091).

Mini Challenge

Zayan's rocket counts down from 3, waiting one second between numbers, then launches. Write an async function countdown(int from) and call it from main.

Your program should print exactly:

output
Zayan's rocket
3
2
1
Liftoff!

Try it yourself before opening the solution.

Show solution
dart
Future<void> countdown(int from) async {
  for (var i = from; i >= 1; i--) {
    print(i);
    await Future.delayed(Duration(seconds: 1));
  }
}

Future<void> main() async {
  print("Zayan's rocket");
  await countdown(3);
  print('Liftoff!');
}
output
Zayan's rocket
3
2
1
Liftoff!

Without await in main, Liftoff! would print right after 3.


Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Write an async function getCity() that returns 'Karachi'. Await it in main and print the result.

  2. What does this program print, and why?

    dart
    Future<String> getCity() async {
      return 'Karachi';
    }
    
    void main() {
      print(getCity());
    }
  3. This program does not compile. Fix main so it prints 40:

    dart
    Future<int> fetchScore() async {
      return 40;
    }
    
    void main() {
      var score = await fetchScore();
      print(score);
    }

Application

  1. Write square(int n) that waits 500 milliseconds and returns n * n. Print the square of 7.

  2. Rewrite this using await, with the same output:

    dart
    Future<int> fetchPrice() {
      return Future.delayed(Duration(seconds: 1), () => 450);
    }
    
    void main() {
      fetchPrice().then((price) => print('Price: $price'));
    }
  3. Predict the exact output order:

    dart
    Future<void> task() async {
      print('B');
      await Future.delayed(Duration(milliseconds: 500));
      print('D');
    }
    
    void main() {
      print('A');
      task();
      print('C');
    }
  4. greet(name) takes one second and returns 'Hello, $name'. Greet Ali, Sara, and Zayan in a for-in loop with await, then print how many seconds it took.

  5. Fix the return type so this compiles and prints 155:

    dart
    int total(int a, int b) async {
      return a + b;
    }

Challenge

  1. Sara's account starts at 2000.0. Write async functions deposit and charge that each wait 300 milliseconds and return the new balance. Deposit 500, charge a fee of 12.5, and print Balance: 2487.5.

  2. This program prints All done before the report is saved. Fix it, and explain in one sentence what was wrong:

    dart
    Future<void> saveReport(String name) async {
      await Future.delayed(Duration(seconds: 1));
      print('Saved report for $name');
    }
    
    Future<void> main() async {
      saveReport('Sara');
      print('All done');
    }

Practice Answers

Question 1 — Solution

dart
Future<String> getCity() async {
  return 'Karachi';
}

Future<void> main() async {
  var city = await getCity();
  print(city);
}
output
Karachi

Question 2 — Solution

output
Instance of 'Future<String>'

An async function always returns a Future. Without await, print receives the Future itself, not the text inside it.

Question 3 — Solution

dart
Future<int> fetchScore() async {
  return 40;
}

Future<void> main() async {
  var score = await fetchScore();
  print(score);
}
output
40

Question 4 — Solution

dart
Future<int> square(int n) async {
  await Future.delayed(Duration(milliseconds: 500));
  return n * n;
}

Future<void> main() async {
  print(await square(7));
}
output
49

Question 5 — Solution

dart
Future<int> fetchPrice() {
  return Future.delayed(Duration(seconds: 1), () => 450);
}

Future<void> main() async {
  var price = await fetchPrice();
  print('Price: $price');
}
output
Price: 450

Question 6 — Solution

output
A
B
C
D

task prints B before its first await, then pauses. main does not wait, so it prints C. Half a second later task resumes and prints D.

Question 7 — Solution

dart
Future<String> greet(String name) {
  return Future.delayed(Duration(seconds: 1), () => 'Hello, $name');
}

Future<void> main() async {
  var watch = Stopwatch()..start();
  for (var name in ['Ali', 'Sara', 'Zayan']) {
    print(await greet(name));
  }
  print('Took about ${watch.elapsed.inSeconds} seconds');
}
output
Hello, Ali
Hello, Sara
Hello, Zayan
Took about 3 seconds

Question 8 — Solution

dart
Future<int> total(int a, int b) async {
  return a + b;
}

Future<void> main() async {
  print(await total(80, 75));
}
output
155

Question 9 — Solution

dart
Future<double> deposit(double balance, double amount) async {
  await Future.delayed(Duration(milliseconds: 300));
  return balance + amount;
}

Future<double> charge(double balance, double fee) async {
  await Future.delayed(Duration(milliseconds: 300));
  return balance - fee;
}

Future<void> main() async {
  var balance = 2000.0;
  balance = await deposit(balance, 500);
  balance = await charge(balance, 12.5);
  print('Balance: $balance');
}
output
Balance: 2487.5

Question 10 — Solution

saveReport was called without await, so main moved on before the save finished.

dart
Future<void> saveReport(String name) async {
  await Future.delayed(Duration(seconds: 1));
  print('Saved report for $name');
}

Future<void> main() async {
  await saveReport('Sara');
  print('All done');
}
output
Saved report for Sara
All done

Quick Revision

  1. What does an async function always return?
  2. What does await give you back from a Future<int>?
  3. Why is main written Future<void> main() async?
  4. Does await pause the whole program?
  5. What prints if you interpolate a Future without awaiting it?
  6. Three one-second awaits in a loop take about how long?
  7. Which lint reports a forgotten await?

Key Takeaways

  • async marks a function as asynchronous; it always returns a Future.
  • Inside it, return the plain value — the caller receives Future<T>.
  • await waits for a Future and gives back its value.
  • await is only allowed inside async functions, so main becomes Future<void> main() async.
  • await pauses only its own function; the code before the first await runs at once.
  • Every .then step becomes one await line, with all values in one scope.
  • await in a loop runs the steps one after another; their times add up.
  • A forgotten await gives Instance of 'Future<…>' or the wrong order — enable unawaited_futures.

What's Next?

Lecture 090 — Future Error Handling. Every Future in this lecture succeeded. Real ones fail: the network drops, an item is out of stock, a file is missing. Next we will see what happens when an awaited Future completes with an error, and how the try/catch from Lecture 084 works around await.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.