The Event Loop
Prerequisites: Futures and async/await
Dart runs your code on a single thread per isolate. Understanding how it juggles asynchronous work explains a lot of behavior — including why a Flutter app can freeze.
One thread, two queues
Dart's event loop repeatedly does this:
- Run the current synchronous code to completion.
- Run all waiting microtasks (short follow-up jobs — for
example, continuing after an
awaiton an already-completed future). - Take the next event (a timer firing, a network response, a user
tap, a
Future(...)) and run its code. Then back to step 2.
import 'dart:async';
void main() {
print('1: synchronous');
Future(() => print('4: event queue (Future)'));
Future.delayed(Duration.zero, () => print('5: event queue (timer)'));
scheduleMicrotask(() => print('3: microtask'));
print('2: synchronous');
}
1: synchronous
2: synchronous
3: microtask
4: event queue (Future)
5: event queue (timer)
All synchronous code runs first, then the microtask, then events in order.
Why this matters: never block the loop
While your code runs, nothing else happens — no other events, no screen redraws. A long synchronous loop freezes a Flutter app:
void main() {
Future(() => print('I have to wait'));
final stopwatch = Stopwatch()..start();
var sum = 0;
for (var i = 0; i < 300000000; i++) {
sum += i;
}
print('Busy loop finished (${sum > 0})');
}
Busy loop finished (true)
I have to wait
The queued event can't run until the loop finishes. async/await
doesn't help with this kind of work — await only frees the thread
while waiting (for I/O or timers), not while computing. For heavy
computation, use an isolate (a later lesson).
Practical rules
- I/O (network, files, databases) is already asynchronous in Dart —
just
awaitit. - Keep synchronous work short, especially in Flutter, where each frame has about 16 milliseconds to render at 60 frames per second.
- Move CPU-heavy work — parsing a huge JSON file, image processing — to another isolate.