Lesson 20 min

The Event Loop

00:00 / 00:00

Dart is Single-Threaded

Dart code runs in a single thread called an isolate. There is no shared memory between isolates, and within one isolate, only one piece of Dart code runs at a time. Yet Dart handles thousands of concurrent operations. How?

The answer is the Event Loop — the mechanism that makes asynchronous programming possible without multiple threads.

The Event Loop Architecture

Dart's event loop manages two queues:

  • Microtask Queue — High priority. Contains short, internal callbacks (like Future.then continuations). Always drained completely before moving to the event queue.
  • Event Queue — Lower priority. Contains I/O events, timer callbacks, user input, and Stream events.
┌─────────────────────────────────────┐
│         Main Dart Code              │  ← runs first
│     (synchronous execution)         │
└──────────────┬──────────────────────┘
               │ completes
               ▼
┌─────────────────────────────────────┐
│         Microtask Queue             │  ← checked first, drained fully
│   (Future completions, scheduleMicrotask) │
└──────────────┬──────────────────────┘
               │ empty
               ▼
┌─────────────────────────────────────┐
│          Event Queue                │  ← one event processed at a time
│   (Timer, I/O, user input, Streams) │
└──────────────┬──────────────────────┘
               └── loop back ──────────┘

Demonstrating Queue Priority

dart
import 'dart:async'; void main() { print('1: Synchronous start'); // Scheduled on the EVENT queue Timer(Duration.zero, () => print('4: Timer callback (event queue)')); // Scheduled on the MICROTASK queue scheduleMicrotask(() => print('3: Microtask')); // Future.value completes immediately → schedules .then on microtask queue Future.value(42).then((v) => print('3b: Future.then (microtask) value=$v')); print('2: Synchronous end'); } // Output: // 1: Synchronous start // 2: Synchronous end // 3: Microtask // 3b: Future.then (microtask) value=42 // 4: Timer callback (event queue)

Why This Matters for Flutter

Flutter renders UI on the same thread (the UI isolate). If your synchronous Dart code takes too long, it blocks the event loop and drops frames:

dart
// BAD — blocks the event loop for 100ms, causes jank void badOperation() { // Expensive synchronous computation final result = expensiveSync(); // blocks for 100ms! setState(() => data = result); } // GOOD — offload to a separate isolate, return result asynchronously void goodOperation() async { final result = await compute(expensiveSync, null); // runs in isolate setState(() => data = result); }

The Golden Rule

Never block the event loop. Any synchronous operation that takes more than ~16ms will cause dropped frames (jank). If you have heavy computation, use Isolate.run() or Flutter's compute() to move it off the UI isolate.

Summary

The event loop is the heart of Dart's concurrency model. Understanding it explains why async/await works the way it does, why microtasks are prioritized over timers, and why blocking synchronous code causes performance problems. Everything in the next three lessons builds on this foundation.

WhatsApp