Skip to main content

Menu

System onlineAI Tutorials / 2026

AI Tutorials
Knowledge search

Recent · Popular · Tutorials

  • dart3 tutorials
  • java1 tutorial
  • Javascript3 tutorials
  • Node.js Introduction2 tutorials
  • dartdart · General · In depth
  • JavascriptJavascript · Advanced · In depth
  • JavascriptJavascript · Advanced · Mid
  • JavascriptJavascript · Advanced · Short

Find a tutorial

Tell the assistant what you want to learn. It suggests published chapters.

For example: “I want to learn Rust ownership from the start.” The conversation is kept only in this tab.

0 / 2,000 characters

Enter to send, Shift+Enter for a new line. Do not include personal information.

Chapter 18

Isolates: Dart's Model of Concurrency

Some programs need to do heavy work: process a large file, crunch numbers, resize hundreds of images. If that work happens on the same thread as your user interface, everything freezes. Dart solves this problem with **isolates**.
Level: GeneralLength: In depth4 min read

Read at another depth

Isolates: Dart's Model of Concurrency

Some programs need to do heavy work: process a large file, crunch numbers, resize hundreds of images. If that work happens on the same thread as your user interface, everything freezes. Dart solves this problem with isolates.

An isolate is an independent unit of work with its own memory and its own single thread of execution. The name is deliberate: isolates cannot share memory with each other. One isolate never reaches into another isolate's variables. Instead, they pass messages back and forth. This chapter explains why Dart works this way, how to use isolates, and when to reach for them.

Why Dart chose isolation

In many languages, multiple threads share the same memory. Any thread can read or change any object at any time. That power comes at a cost: two threads can modify the same value at once, causing bugs that are famously hard to reproduce and fix.

Dart takes a different path. Each isolate owns its memory completely. No locks, no shared state, no race conditions between isolates. The price is that communication happens through messages, which are copies of data sent from one isolate to another. If you want another isolate to see your data, you send it a copy (or, in modern Dart, certain immutable objects can be shared directly, but copying is the common case).

The event loop in every isolate

Every isolate runs a single thread that processes an event loop. The isolate takes one event at a time, finishes it, and moves to the next. This is why async and await work without locks: while one piece of code waits for a network response, the isolate can handle other events, but only one thing runs at a time.

Code example in dart
Future<void> main() async {
  print('one');
  await Future.delayed(Duration(milliseconds: 100));
  print('two');
}

Here a single isolate prints one, waits without blocking, then prints two. The event loop kept everything orderly. But if you compute something slow, nothing else in that isolate can run until the computation finishes. That is exactly when you spawn another isolate.

Spawning an isolate

Isolate.spawn starts a new isolate running a given function. Isolates talk through ports: a SendPort for sending and a ReceivePort for receiving.

Code example in dart
import 'dart:isolate';

void worker(SendPort sendPort) {
  // Heavy work happens here, in its own memory.
  var total = 0;
  for (var i = 0; i < 2000000000; i++) {
    total += i;
  }
  sendPort.send(total);
}

Future<void> main() async {
  final receivePort = ReceivePort();
  await Isolate.spawn(worker, receivePort.sendPort);
  final result = await receivePort.first;
  print('The total is $result');
}

Read the diagram, then note what happens in the code:

  1. The main isolate creates a ReceivePort to listen for messages.
  2. It spawns a new isolate, passing along the port to send replies to.
  3. The worker isolate computes the sum and sends the result.
  4. await receivePort.first receives the message and closes the port.
Diagram showing a Main isolate box and a Worker isolate box. An arrow labeled spawn goes from Main to Worker. An arrow labeled send port, message goes from Worker back to Main.
100%
Two isolates communicating through a message port
Show diagram source
sequenceDiagram
    participant Main as Main isolate
    participant Worker as Worker isolate
    Main->>Worker: Isolate.spawn(worker, sendPort)
    Worker->>Worker: heavy computation (own memory)
    Worker->>Main: sendPort.send(result)
    Main->>Main: await receivePort.first

The easier way: `compute`-style helpers

Managing ports by hand is verbose. For a one-shot calculation, the modern standard library offers Isolate.run, which handles spawning, communication, and cleanup for you:

Code example in dart
import 'dart:isolate';

int sumRange(int n) {
  var total = 0;
  for (var i = 0; i <= n; i++) {
    total += i;
  }
  return total;
}

Future<void> main() async {
  final result = await Isolate.run(() => sumRange(2000000000));
  print('The total is $result');
}

The closure runs on a fresh isolate, and its return value is sent back automatically. Prefer this form whenever you simply have one expensive function.

What can and cannot be sent

Messages between isolates must be sendable:

  • Numbers, strings, booleans, lists, maps, and other simple values: fine, copied.
  • Objects of your own classes: generally fine in recent Dart versions, copied.
  • Closures capturing local state, and open handles like ReceivePort: not sendable, and Dart will throw an error if you try.

A useful mental model: assume everything is copied. If two isolates each have "the same" list, they are really two independent lists. Changing one does not change the other.

When to use isolates

Use an isolate when:

  • A computation takes more than a few milliseconds and runs on the main isolate of a UI (for example, parsing a large JSON document).
  • You want to keep one isolate responsive while work happens in the background.

Do not use an isolate when:

  • The work is mostly waiting: file reads, network calls, timers. Async code with await already handles waiting efficiently on the same isolate.
  • The work is trivial. Spawning an isolate copies data and costs time, so for tiny tasks it is a net loss.

A rough rule: await for waiting, isolates for calculating.

Errors and cleanup

If an isolate crashes, it does not take your program down automatically; you should listen for errors. Also, close your ReceivePort when done to avoid leaking resources.

Code example in dart
import 'dart:isolate';

void risky(SendPort sendPort) {
  sendPort.send('about to fail');
  throw StateError('worker crashed');
}

Future<void> main() async {
  final receivePort = ReceivePort();
  final errorPort = ReceivePort();
  errorPort.listen((message) {
    print('error arrived: $message');
  });
  await Isolate.spawn(risky, receivePort.sendPort,
      onError: errorPort.sendPort);
  print(await receivePort.first);
}

The onError port receives errors thrown by the worker as messages, letting the main isolate report or recover from failures.

Putting it together

Dart's concurrency story has two layers. async/await keep one isolate responsive while waiting on the outside world. Isolates give you true parallelism by giving each worker its own memory and message-passing communication. Together they let you write code that is both easy to reason about and fast under load.

Exercise

Why can two isolates modify a shared list without causing race conditions?

Show answer

They cannot share it at all. When a list is sent between isolates, each isolate receives its own copy. Since no memory is shared, there is no possibility of two threads changing the same value at the same time.

Exercise

You need to download a file and then count how often each word appears in it. Which parts belong in async code and which in an isolate?

Show answer

The download is waiting on the network, so use async code with await on the main isolate. Parsing the text and counting words is CPU-heavy calculation, so run that in an isolate with Isolate.run, returning the result via the await.

Spotted a mistake, or code that does not work?

Report an issue

Chapter: Isolates: Dart's Model of Concurrency

0 / 2,000 characters

Plain text, for example: the second code sample does not compile. Do not include personal information.

You can't do that