Event Loop Architecture
Every Dart Isolate runs on a single thread driven by an Event Loop. Understanding how the Dart Event Loop processes tasks is essential for avoiding UI jank, dropped frames, and unexpected execution order in Flutter applications.
1. The Two-Queue Architecture
The Dart Event Loop manages two FIFO (First-In, First-Out) queues:
- Microtask Queue: Contains short, high-priority internal tasks that must be executed synchronously before yielding back to external events.
- Event Queue: Contains external events, including I/O operations, timer events, user interaction taps, graphics rendering requests, and standard
Futurecompletion callbacks.
ββββββββββββββββββββββββββ
β Dart Event Loop β
βββββββββββββ¬βββββββββββββ
β
Is Microtask Queue empty?
ββββ NO βββΊ Execute ALL Microtasks (Priority 1)
β
YES
β
Pop 1 Event from Event Queue βββΊ Execute Event Callback (Priority 2)2. Under The Hood: Queue Execution Priority Rules
The Golden Rule of Execution Priority
The Event Loop will NEVER pick an item from the Event Queue until the Microtask Queue is COMPLETELY EMPTY.
After processing a single event from the Event Queue, the Event Loop pauses, checks the Microtask Queue again, and drains ALL pending microtasks before processing the next item from the Event Queue.
Task Scheduling APIs
| API | Target Queue | Priority | Common Usage |
|---|---|---|---|
scheduleMicrotask(() {}) | Microtask Queue | High | Internal state cleanup, deferred internal assertions |
Future.microtask(() {}) | Microtask Queue | High | Priority completion before next event tick |
Future(() {}) | Event Queue | Normal | Asynchronous work, network/disk continuations |
Timer.run(() {}) | Event Queue | Normal | Scheduling callbacks on next event loop cycle |
Timer(duration, () {}) | Event Queue | Delayed | Delayed execution |
3. Flutter Frame Rendering & Queue Starvation
Flutterβs engine relies on the Event Queue to process frame rendering triggers requested by the operating systemβs Vsync pulse.
UI Jank & Starvation Mechanics
If your code continuously schedules microtasks (or enters a recursive scheduleMicrotask loop), the Microtask Queue never drains to zero.
- Consequence: The Event Loop is blocked from popping frame rendering events or touch gestures from the Event Queue.
- Symptom: The UI completely freezes (0 FPS), animations stutter, and the app becomes unresponsive to user taps.
// Execution Order Demonstration:
void main() {
print('1: Main Start');
Future(() => print('5: Event Queue (Future)'));
Future.microtask(() => print('3: Microtask 1'));
scheduleMicrotask(() => print('4: Microtask 2'));
print('2: Main End');
}
// Output:
// 1: Main Start
// 2: Main End
// 3: Microtask 1
// 4: Microtask 2
// 5: Event Queue (Future)4. Trade-offs & Production Considerations
- Microtask Precision vs UI Responsiveness: Microtasks ensure high-priority logic completes prior to the next I/O tick, but queuing heavy or recursive microtasks starves the Event Queue.
- Frame Budget Constraints: At 60 FPS, each Flutter frame must complete within 16.6ms (or 8.3ms at 120 FPS). Long-running synchronous calculations or microtasks that exceed this budget drop frames instantly.