Watch the video (10 min)
On this page23 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is a Future?
  5. 2. Printing a Future
  6. 3. The Three States of a Future
  7. 4. Getting the Value with .then
  8. 5. The Program Does Not Wait
  9. 6. Future.value — Already Completed
  10. 7. Future(() => ...) — Run Work Later
  11. 8. Chaining .then Calls
  12. 9. Returning a Future from a Function
  13. 10. Future<void> — Work with No Result
  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 88 — Futures

Category15 — Asynchronous Programming
Lecture088 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelIntermediate

A slow task cannot hand back its result straight away — but making the program wait would freeze it. A Future hands back a promise of the result instead, and lets you say what should happen when it arrives.

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.


Prerequisites

Students should already understand, from Lectures 030–087:

  • functions, return types, and arrow and anonymous functions (Lectures 030–036)
  • generic types such as List<int> (Lectures 072–073)
  • exceptions (Lectures 083–086)
  • blocking code, the event loop, and a first look at Future.delayed (Lecture 087)

Connection With Previous Lectures

In Lecture 087 you saw that a slow task — downloading data, reading a file — must not block the program. Dart starts the task, keeps running the rest of the code, and comes back when the task is done.

That raises a question. A normal function hands back its result with return. If the result is not ready yet, what does the function hand back? And how does the rest of the program get the value once it finally exists? The answer to both is a Future.


Learning Objectives

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

  • Explain what a Future<T> represents
  • Name the three states a future can be in
  • Create futures with Future.delayed, Future.value, and Future(() => ...)
  • Use .then to run code when a future's value arrives
  • Predict the order of output when futures are involved
  • Chain several .then calls
  • Write functions that return Future<T> and Future<void>
  • Recognize the errors Dart gives when a future is used as a plain value

1. What Is a Future?

A Future<T> is an object that promises a value of type T later. A Future<int> will one day hold an int; a Future<String> will hold a String.

Analogy — at a busy café you order a coffee and get a receipt with a number. The receipt is not the coffee, but it promises one. You sit down and do other things, and when your number is called, you collect the real coffee. A Future is that receipt.


2. Printing a Future

Throughout this lecture we simulate a slow network call with Future.delayed. It waits for a Duration, then runs a function and uses its result as the value:

dart
void main() {
  var marks = Future.delayed(Duration(seconds: 2), () => 85);
  print(marks);
}
output
Instance of 'Future<int>'

We did not get 85. marks is the receipt, not the coffee. Dart infers its type as Future<int> from the function's result, and printing it describes the object.


3. The Three States of a Future

A future is always in one of three states:

StateMeaningCafé
Uncompletedthe work is still runningcoffee is being made
Completed with a valuethe result is readyyour coffee is served
Completed with an errorthe work failed"sorry, we are out of milk"

In section 2, marks was uncompleted when we printed it; two seconds later it completed with the value 85. A future completes once and then never changes. Handling the error case is the topic of Lecture 090 — Future Error Handling.


4. Getting the Value with .then

To use the value, give the future a function with .then. Dart calls that function with the value as soon as the future completes:

dart
void main() {
  var marks = Future.delayed(Duration(seconds: 2), () => 85);
  marks.then((value) {
    print('Marks arrived: $value');
  });
}
output
Marks arrived: 85

The text appears after a two-second pause. .then is like telling the café, "call my number when it's ready."


5. The Program Does Not Wait

.then only registers the function. The next line runs immediately:

dart
void main() {
  print('1. Asking for marks');
  Future.delayed(Duration(seconds: 2), () => 85).then((value) {
    print('3. Marks: $value');
  });
  print('2. Doing other work');
}
output
1. Asking for marks
2. Doing other work
3. Marks: 85

Line 2 is printed before line 3, even though it comes later in the code. This is the non-blocking behavior from Lecture 087, now with a value at the end of it.


6. Future.value — Already Completed

Future.value creates a future that already has its value. It is handy when a function must return a future but the answer is known:

dart
void main() {
  var name = Future.value('Sara');
  print('Before');
  name.then((value) => print('Name: $value'));
  print('After');
}
output
Before
After
Name: Sara

Even with the value ready, the .then function still runs after the current code finishes. A .then callback never interrupts the line-by-line code that is running.


7. Future(() => ...) — Run Work Later

The Future constructor takes a function, runs it soon (but not right now), and completes with its result:

dart
void main() {
  var total = Future(() => 500 * 3);
  total.then((value) => print('Total: $value'));
  print('Calculating...');
}
output
Calculating...
Total: 1500
ConstructorCompletes
Future.delayed(duration, () => v)after the duration
Future.value(v)already holds v
Future(() => v)as soon as the current code finishes

8. Chaining .then Calls

.then itself returns a new future that completes with whatever your function returns. So you can attach another .then, each step receiving the previous step's result:

dart
void main() {
  Future.delayed(Duration(seconds: 1), () => 80)
      .then((marks) => marks + 5)
      .then((withBonus) => 'Final marks: $withBonus')
      .then((message) => print(message));
}
output
Final marks: 85

80 becomes 85, 85 becomes a String, and the last step prints it.


9. Returning a Future from a Function

A function that does slow work returns a Future<T>. Its return type says "you will get a T, later":

dart
Future<int> fetchBalance() {
  return Future.delayed(Duration(seconds: 2), () => 1000);
}

void main() {
  print('Loading balance...');
  fetchBalance().then((balance) => print('Balance: $balance'));
}
output
Loading balance...
Balance: 1000

The function itself returns at once — with the receipt. The caller uses .then to collect the 1000.


10. Future<void> — Work with No Result

Some slow work produces nothing useful, such as saving a file. Its type is Future<void>: the future only tells you when the work is finished.

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

void main() {
  saveScore(42).then((_) => print('All done'));
}
output
Saved score 42
All done

There is no value to receive, so the .then parameter is named _.


11. A Real-World Example

An app loads a student's name, then that student's marks — two simulated network calls, one after the other:

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

Future<List<int>> fetchMarks(String name) {
  return Future.delayed(Duration(seconds: 1), () => [80, 75, 85]);
}

void main() {
  print('Loading report...');
  fetchStudent()
      .then((name) {
        print('Student: $name');
        return fetchMarks(name);
      })
      .then((marks) {
        var total = 0;
        for (var mark in marks) {
          total += mark;
        }
        print('Marks: $marks');
        print('Total: $total');
      });
  print('Screen stays responsive');
}
output
Loading report...
Screen stays responsive
Student: Ali
Marks: [80, 75, 85]
Total: 240

The first .then returns another future. Dart waits for it and passes its value — the list — to the next .then. Meanwhile main has already printed its last line.

Chains of .then get hard to read quickly. In Lecture 089 you will write the same program with async and await, top to bottom like normal code.


Code Examples

Example 1 — Printing a Future

dart
void main() {
  var city = Future.value('Karachi');
  print(city);
}
output
Instance of 'Future<String>'

Example 2 — Reading the Value

dart
void main() {
  var city = Future.value('Karachi');
  city.then((value) => print(value));
}
output
Karachi

Example 3 — A Delayed Value

dart
void main() {
  Future.delayed(
    Duration(milliseconds: 500),
    () => 'Ready',
  ).then((value) => print(value));
  print('Waiting...');
}
output
Waiting...
Ready

Example 4 — The Future Constructor

dart
void main() {
  Future(() => 12 * 12).then((value) => print('Square: $value'));
  print('First line');
}
output
First line
Square: 144

Example 5 — A Chain of Three Steps

dart
void main() {
  Future.value(10).then((n) => n * 2).then((n) => n + 1).then((n) => print(n));
}
output
21

Example 6 — A Future<void> Function

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

void main() {
  greet('Zayan').then((_) => print('Greeting finished'));
}
output
Hello, Zayan
Greeting finished

Example 7 — Student Marks

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

void main() {
  fetchMarks('Sara').then((marks) {
    var result = marks >= 50 ? 'Pass' : 'Fail';
    print('Sara: $marks ($result)');
  });
}
output
Sara: 78 (Pass)

How it works

  1. fetchMarks returns a Future<int> that completes with 78 after one second.
  2. The .then function receives 78 and decides the result.

Example 8 — Shopping Total

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

void main() {
  var quantity = 3;
  fetchPrice('Notebook').then((price) {
    var total = price * quantity;
    print('Total: $total');
  });
}
output
Total: 1500

How it works

  1. The price arrives after one second.
  2. The .then function multiplies it by quantity and prints the total.

Example 9 — Bank Balance

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

void main() {
  print('Checking balance...');
  fetchBalance().then((balance) {
    var afterDeposit = balance + 250.5;
    print('Balance: $afterDeposit');
  });
  print('You can keep using the app.');
}
output
Checking balance...
You can keep using the app.
Balance: 1250.5

How it works

  1. main prints its two lines straight away.
  2. Two seconds later the balance arrives and the deposit is added.

Example 10 — Temperature from a Weather Service

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

void main() {
  fetchCelsius('Lahore')
      .then((celsius) => celsius * 9 / 5 + 32)
      .then((fahrenheit) => print('Lahore: $fahrenheit °F'));
}
output
Lahore: 77.0 °F

How it works

  1. The "weather service" answers with 25.0 after one second.
  2. The chain converts it to Fahrenheit, then prints it.

Common Beginner Mistakes

1. Expecting the value instead of the future

dart
Future<int> fetchMarks() {
  return Future.delayed(Duration(seconds: 2), () => 85);
}

void main() {
  // int marks = fetchMarks(); // ✗
}
output
Error: A value of type 'Future<int>' can't be assigned to a variable of type 'int'.

A future is not its value. Use fetchMarks().then((marks) => ...).

2. Forgetting Future in the return type

dart
// int fetchMarks() {
//   return Future.delayed(Duration(seconds: 1), () => 85); // ✗
// }
output
Error: A value of type 'Future<int>' can't be returned from a function with return type 'int'.

A function that returns a future must say so: Future<int> fetchMarks().

3. Expecting code after .then to wait

dart
void main() {
  var total = 0;
  Future.value(500).then((price) => total = price);
  print(total); // prints 0, not 500
}

The print runs before the .then function. Code that needs the value belongs inside the .then.

4. Mismatching the future's type

dart
void main() {
  // Future<String> name = Future.delayed(Duration(seconds: 1), () => 85); // ✗
}
output
Error: A value of type 'int' can't be returned from a function with return type 'FutureOr<String>'.

A Future<String> must complete with a String. FutureOr<String> means "a String or a future of one."


Best Practices

  • Give functions that do slow work an explicit Future<T> return type.
  • Use Future<void> when the caller only needs to know the work is finished.
  • Put code that depends on a future's value inside .then, not after it.
  • Use Future.delayed to simulate slow work while learning and testing.
  • Keep .then chains short — Lecture 089 offers a cleaner style.

Mini Challenge

Write Future<int> fetchStock(String item) that "returns" 12 after one second. In main, print Checking stock..., then print the stock when it arrives.

output
Checking stock...
Pens in stock: 12

Try it yourself before opening the solution.

Show solution
dart
Future<int> fetchStock(String item) {
  return Future.delayed(Duration(seconds: 1), () => 12);
}

void main() {
  print('Checking stock...');
  fetchStock('Pens').then((stock) => print('Pens in stock: $stock'));
}
output
Checking stock...
Pens in stock: 12

Practice Questions

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

Beginner

  1. Create Future.value(7) and print the future itself. What does the output say?
  2. Create Future.value('Ali') and use .then to print Hello, Ali.
  3. Use Future.delayed to produce 'Done' after one second, and print it with .then.

Application

  1. Predict the output, then run it:

    dart
    void main() {
      print('A');
      Future.value('B').then((v) => print(v));
      print('C');
    }
  2. Write Future<int> fetchScore() that completes with 40 after one second. In main, print Score: 40.

  3. Starting from Future.value(100), chain .then calls that subtract 30, then double the result, then print it.

  4. Write Future<void> sendMessage(String text) that prints Sent: <text> after one second. Call it with 'Hi Sara' and print Finished when it completes.

Challenge

  1. Write Future<double> fetchPrice() that completes with 1200.0 after one second. Apply a 10% discount in one .then and print Final price: 1080.0 in the next.

  2. Write fetchUser() ('Zayan' after one second) and fetchAge(String name) (6 after one second). Chain them to print Zayan is 6.

  3. This program prints Total: 0. Fix it so it prints Total: 900:

    dart
    void main() {
      var total = 0;
      Future.value(450).then((price) => total = price * 2);
      print('Total: $total');
    }

Practice Answers

Question 1 — Solution

dart
void main() {
  print(Future.value(7));
}
output
Instance of 'Future<int>'

It describes the future, not the value 7.

Question 2 — Solution

dart
void main() {
  Future.value('Ali').then((name) => print('Hello, $name'));
}
output
Hello, Ali

Question 3 — Solution

dart
void main() {
  Future.delayed(
    Duration(seconds: 1),
    () => 'Done',
  ).then((value) => print(value));
}
output
Done

Question 4 — Solution

output
A
C
B

The .then function runs only after main's own lines have finished.

Question 5 — Solution

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

void main() {
  fetchScore().then((score) => print('Score: $score'));
}
output
Score: 40

Question 6 — Solution

dart
void main() {
  var start = Future.value(100);
  start.then((n) => n - 30).then((n) => n * 2).then((n) => print(n));
}
output
140

Question 7 — Solution

dart
Future<void> sendMessage(String text) {
  return Future.delayed(Duration(seconds: 1), () => print('Sent: $text'));
}

void main() {
  sendMessage('Hi Sara').then((_) => print('Finished'));
}
output
Sent: Hi Sara
Finished

Question 8 — Solution

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

void main() {
  fetchPrice()
      .then((price) => price - price * 0.1)
      .then((finalPrice) => print('Final price: $finalPrice'));
}
output
Final price: 1080.0

Question 9 — Solution

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

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

void main() {
  fetchUser().then((name) {
    return fetchAge(name).then((age) => print('$name is $age'));
  });
}
output
Zayan is 6

The inner .then is nested so it can still use name.

Question 10 — Solution

Move the print inside the .then, where the value exists:

dart
void main() {
  Future.value(450).then((price) {
    var total = price * 2;
    print('Total: $total');
  });
}
output
Total: 900

Quick Revision

  1. What does a Future<String> promise?
  2. What are the three states of a future?
  3. What does print show when you print a Future<int>?
  4. Which constructor creates a future that already has its value?
  5. Does the line after a .then call wait for the future?
  6. What does .then itself return?
  7. When should a function return Future<void>?

Key Takeaways

  • A Future<T> is a promise of a T that arrives later.
  • A future is uncompleted, completed with a value, or completed with an error — and completes only once.
  • Printing a future shows Instance of 'Future<int>', not the value.
  • Create futures with Future.delayed, Future.value, and Future(() => ...).
  • .then registers code to run when the value arrives; the program does not wait.
  • .then returns a new future, so calls can be chained.
  • Slow functions return Future<T>, or Future<void> when there is no result.

What's Next?

Lecture 089 — async & await. .then works, but every step adds another callback, and the code no longer reads top to bottom. Next, async and await let you wait for a future's value in a straight line — var marks = await fetchMarks(); — without blocking the program.


Video lecture

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