You write your first Flutter app that pulls data from the internet, and something strange happens. You ask for a username, and instead of the name you get this back: Instance of 'Future<String>'. Or worse, your screen locks up for a second while everything waits on one slow request. That moment is where Dart async await stops being optional and starts being the thing you reach for every day. Almost everything interesting an app does, loading data, reading a file, talking to a server, takes time, and Dart gives you a clean way to handle that wait instead of fighting it. By the end of this post you will know what a Future really is, how await pauses just the right part of your code, how to run two slow jobs at once, and how to catch errors before they take down your app. We will use tiny examples you can paste and run, and you will see the output at every step.
What a Future actually is
A Future is Dart’s way of saying “I do not have this value yet, but I will soon.” Think of it like a coat check ticket. You hand over your coat, you get a little numbered stub, and that stub is not the coat. It is a promise that you can get the coat back later. A Future<String> is a promise that a String is coming. A Future<int> promises an int. The value is not here yet, and Dart is honest with you about that.
Here is a function that pretends to fetch a username from a server. The real version would call the network, but Future.delayed lets us fake a two second wait so the example runs anywhere.
Future<String> fetchUsername() {
return Future.delayed(Duration(seconds: 2), () => 'mosh_codes');
}
void main() {
print('Asking for the username...');
print(fetchUsername()); // this prints the ticket, not the coat
print('This line does not wait.');
}
Run that and here is what you get, instantly, with no two second pause:
Asking for the username...
Instance of 'Future<String>'
This line does not wait.
Look at that middle line. You asked Dart to print the result of fetchUsername(), and it printed Instance of 'Future<String>' instead of the name. That is the coat check ticket. The function handed back a Future immediately and went right on to the next line. The actual username is still two seconds out. This is the exact thing that confuses people on day one, and once you see it for what it is, the fix makes sense.
How Dart async await works, step by step
To get the value out of a Future, you wait for it, and the two keywords that do that are async and await. You mark a function async, and inside it you put await in front of anything that returns a Future. When Dart hits an await, it pauses that one function until the value is ready, then picks up right where it left off. Same example, fixed:
Future<String> fetchUsername() {
return Future.delayed(Duration(seconds: 2), () => 'mosh_codes');
}
Future<void> main() async {
print('Asking for the username...');
String name = await fetchUsername();
print('Got it: $name');
print('Now I can use the name.');
}
Now the output looks like you would hope, with a two second gap landing right where the wait happens:
Asking for the username...
Got it: mosh_codes
Now I can use the name.
The word await is doing the work. It unwraps the Future and hands you the plain String, so name holds 'mosh_codes', not a ticket. Two details trip up almost every first semester student here. First, you can only use await inside a function marked async. Leave off the async and the compiler will stop you cold. Second, the rest of your program does not freeze during that wait. Only this function pauses. The rest of your app keeps drawing, animating, and responding.
The part that surprises people
Here is the bit that feels like magic until it clicks. An await does not block everything around it. Watch this:
Future<void> main() async {
print('1: before');
Future.delayed(Duration(seconds: 1), () => print('3: inside the future'));
print('2: after');
}
1: before
2: after
3: inside the future
You might expect the lines to print in the order you wrote them. They do not. The two plain prints run first, top to bottom, and the delayed one fires a second later, after the regular code has already finished. That is the whole point of asynchronous code. Slow work steps aside and lets the rest of your program keep moving instead of making everyone wait in line behind it.

Doing things in order, or all at once
Once you have more than one slow job, you get a choice, and it is worth understanding because it directly affects how fast your app feels. Say you are fetching two scores, and each one takes a second.
Future<int> fetchScore(String name) {
return Future.delayed(Duration(seconds: 1), () => name.length * 10);
}
Future<void> main() async {
final start = DateTime.now();
// Approach 1: one after the other
int a = await fetchScore('Ada');
int b = await fetchScore('Grace');
print('Sequential total: ${a + b}');
print('Took about ${DateTime.now().difference(start).inSeconds}s');
}
Sequential total: 80
Took about 2s
Two seconds, because you waited for Ada to finish before you even started Grace. Sometimes that is exactly right, like when the second request needs a result from the first. But here the two fetches do not depend on each other, so making one wait for the other is wasted time. Future.wait fixes that. You hand it a list of Futures, it kicks them all off together, and it gives you back a list of results once every one has finished.
Future<void> main() async {
final start = DateTime.now();
// Approach 2: start both, then wait together
final results = await Future.wait([
fetchScore('Ada'),
fetchScore('Grace'),
]);
print('Parallel total: ${results[0] + results[1]}');
print('Took about ${DateTime.now().difference(start).inSeconds}s');
}
Parallel total: 80
Took about 1s
Same total, half the wait. Both fetches ran at the same time, so the whole thing finished in one second instead of two. When you have a handful of independent requests to make, reaching for Future.wait is one of the easiest speed wins there is. The rule of thumb is simple: if one request needs the answer from another, await them in order, and if they stand alone, run them together.

When things go wrong: catching errors
Real networks fail. Servers time out, the wifi drops, a request comes back empty. A Future that fails does not return a value, it throws, and if you do not catch that throw your app stops. The good news is you already know the tool, because it is the same try and catch you use everywhere else. Wrap the await and you are covered.
Future<String> fetchUsername() {
return Future.delayed(
Duration(seconds: 1),
() => throw Exception('Network is down'),
);
}
Future<void> main() async {
try {
String name = await fetchUsername();
print('Got it: $name');
} catch (e) {
print('Something went wrong: $e');
}
print('The app keeps running.');
}
Something went wrong: Exception: Network is down
The app keeps running.
The fetch threw, the catch grabbed it, and your last line still ran. That final print matters more than it looks. Without the try block, the error would bubble up and the code after it would never run, which in a real app means a frozen screen or a red error box in front of your user. Catch the error, show a friendly message, and let the app carry on. Get in the habit of wrapping any await that touches the outside world, because the outside world will let you down eventually.
Where to go next
You now have the whole core of asynchronous Dart. A Future is a promise of a value that is not ready yet. You mark a function async and use await to pause just that function until the value arrives. You await jobs in order when one depends on another, and you hand independent jobs to Future.wait to run them together. And you wrap anything that can fail in try and catch so one bad request does not sink the app. That is the core of Dart async await, and it covers nearly every data call you will write in Flutter.
The best way to make this stick is to change the examples and watch what happens. Bump the delays up and down. Add a third fetch to the Future.wait list. Make the username fetch fail on purpose and confirm your catch runs. If you want more reps on shaping the data once it arrives, the Dart list methods post pairs perfectly with this, since most of what you fetch comes back as a list. When you start wiring this into a screen, the Flutter ListView guide shows where these results actually go, and the common Flutter mistakes post has a few async traps worth seeing before they cost you an evening. When you are ready for the official word, the Dart async tutorial and the dart:async library docs are both friendly reads. Open your editor, paste in the username example, and run it once with the wait and once without. Seeing the gap appear and disappear is what turns this from a rule you memorized into something you actually understand.

