Async in Dart
One thread, an event loop
Section titled “One thread, an event loop”Dart runs your code on a single thread with an event loop — the same model as JavaScript. There is no shared-memory threading in your normal code. Instead, async operations (network, files, timers) run elsewhere and schedule a callback on the event loop when they finish.
This is why “async” in Flutter does not mean “another thread.” It means “don’t block the one thread we have.” A slow synchronous loop freezes the UI; an await on a network call does not, because the thread is free to render frames while waiting.
flowchart LR ui["UI thread (event loop)"] -->|await fetch| free["thread is free to render frames"] free -->|response arrives| cb["callback scheduled on the loop"] cb --> resume["your code resumes"]
Future and async/await
Section titled “Future and async/await”A Future<T> is a value that will exist later — the result of an async operation. async/await lets you write it as if it were sequential.
Future<User> fetchUser(int id) async { final response = await http.get(Uri.parse('/users/$id')); // waits, doesn't block return User.fromJson(response.body);}
// Calling it:Future<void> load() async { try { final user = await fetchUser(42); print(user.name); } catch (e) { print('failed: $e'); // awaited futures throw errors you can try/catch }}An async function always returns a Future. await unwraps it, and — crucially — errors from an awaited future surface as normal exceptions you can try/catch. In Flutter you rarely await in build; instead you hand the Future to a FutureBuilder (a later module).
Stream: many values over time
Section titled “Stream: many values over time”A Stream<T> is like a Future that delivers many values — a sequence of events over time: web-socket messages, sensor readings, user input, database changes.
Stream<int> ticker() async* { // async* makes a Stream; yield emits a value for (var i = 1; i <= 3; i++) { await Future.delayed(const Duration(seconds: 1)); yield i; // emits 1, then 2, then 3 }}
// Consume with await-for or .listen:await for (final tick in ticker()) { print(tick); // 1, 2, 3 — one per second}The Future/Stream split maps cleanly to Flutter: one-shot async → FutureBuilder; a stream of updates → StreamBuilder. Getting this distinction right is most of what “async in Flutter” means.
Isolates: true parallelism, when you need it
Section titled “Isolates: true parallelism, when you need it”The one exception to “single thread” is isolates — separate workers with their own memory that communicate by message passing (no shared state). You reach for one only for genuinely CPU-heavy work (parsing a huge JSON, image processing) that would otherwise jank the UI. Flutter wraps the common case in compute(), covered in the performance module.