Skip to content

Isolates & compute

async/await fixes waiting (network, disk) — but it does not help with CPU-heavy work. Parsing a 5MB JSON, resizing an image, or running a crypto hash is synchronous computation, and it runs on the UI isolate. While it runs, the event loop can’t render frames, and the app freezes.

You cannot await your way out of this, because there is nothing to wait for — the CPU is busy. The fix is to move the work to a different isolate so the UI isolate stays free to render.

For the everyday “run this expensive pure function off the main isolate,” Flutter gives you compute(). Pass it a top-level (or static) function and its argument; it spins up a background isolate, runs the function there, and returns the result as a Future.

// A top-level function — required, because it must run on another isolate.
List<Item> parseItems(String jsonStr) {
final raw = jsonDecode(jsonStr) as List;
return raw.map((e) => Item.fromJson(e)).toList(); // heavy work
}
// On the UI isolate — offload it, and await the result without janking:
final items = await compute(parseItems, hugeJsonString);

The UI keeps rendering at 60fps while the parse runs elsewhere. compute is the right tool ~90% of the time.

For long-lived workers or two-way communication, use Isolate.spawn with ports — isolates don’t share memory, so they talk by sending messages over SendPort/ReceivePort.

flowchart LR
  main["UI isolate
(keeps rendering)"] -->|send message| worker["Worker isolate
(does CPU work)"]
  worker -->|send result back| main
  note["No shared memory:
only copied messages cross"] --- worker
Offloading heavy work to a worker isolate

The rule that shapes everything: isolates do not share memory. Anything you send is copied across the boundary (with some fast-path exceptions like TransferableTypedData). Consequences:

  • You can’t pass a closure that captures UI state, or a live object graph you keep mutating.
  • Sending very large data has a copy cost — sometimes the copy eats the savings.
  • The worker function must be self-contained (top-level/static), taking plain data in and returning plain data out.

This is the same message-passing model from the Dart async lesson, now used for parallelism instead of concurrency.

Why does async/await NOT fix jank from parsing a huge JSON?
What does compute() do?
How do isolates communicate?
What is a consequence of the no-shared-memory boundary?