Flutter Interview Handbook

Event Loop Architecture

100%

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:

  1. Microtask Queue: Contains short, high-priority internal tasks that must be executed synchronously before yielding back to external events.
  2. Event Queue: Contains external events, including I/O operations, timer events, user interaction taps, graphics rendering requests, and standard Future completion 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

APITarget QueuePriorityCommon Usage
scheduleMicrotask(() {})Microtask QueueHighInternal state cleanup, deferred internal assertions
Future.microtask(() {})Microtask QueueHighPriority completion before next event tick
Future(() {})Event QueueNormalAsynchronous work, network/disk continuations
Timer.run(() {})Event QueueNormalScheduling callbacks on next event loop cycle
Timer(duration, () {})Event QueueDelayedDelayed 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.