Dart: Dart Isolates
Last updated: 2026-08-26
Isolate is Dart's way of doing parallelism — no shared memory, naturally race-free, safe, and efficient.
1. What You'll Learn
- Isolate vs Thread: A concurrency model without shared memory
- Isolate.spawn with ReceivePort / SendPort for bidirectional communication
- Isolate.run: A simplified API for one-shot tasks
- The Compute helper function and Flutter's compute
- Bob's Scenario: DataPipeline using Isolates to process millions of orders in parallel
2. A Developer's Real Story
(1) The Pain: Processing a Million Records Too Slowly in a Single Thread
Bob's DataPipeline needed to perform statistical analysis on 1,200,000 orders. Single-threaded processing took 15 seconds, but SaaS clients required the report to be generated within 5 seconds. Bob tried multithreading, but the shared memory locks and race conditions led him to spend a week fixing concurrency bugs before finally giving up. The report still took 15 seconds.
(2) The Isolate Solution
Dart's Isolates are parallel units without shared memory. Each Isolate has its own independent heap memory and communicates via message passing. No shared memory means no race conditions.
// Split 1.2M orders into 4 Isolates, each processes 300K
final results = await Future.wait([
Isolate.run(() => processChunk(orders.sublist(0, 300000))),
Isolate.run(() => processChunk(orders.sublist(300000, 600000))),
Isolate.run(() => processChunk(orders.sublist(600000, 900000))),
Isolate.run(() => processChunk(orders.sublist(900000, 1200000))),
]);
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
(3) The Benefits
- 4 Isolates running in parallel reduced processing time from 15 seconds to 4 seconds
- No shared memory = No race conditions = No locks = No concurrency bugs
- The message-passing model makes the code easier to reason about
3. Isolate Fundamentals
(1) Isolate vs Thread
flowchart TD
A[Main Isolate] -->|"SendPort"| B[Worker Isolate 1]
A -->|"SendPort"| C[Worker Isolate 2]
A -->|"SendPort"| D[Worker Isolate N]
B -->|"SendPort"| A
C -->|"SendPort"| A
D -->|"SendPort"| A
subgraph Data Sharding
B -- 300K orders
C -- 300K orders
D -- 400K orders
end
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
| Dimension | Thread (Java/C++) | Isolate (Dart) |
|---|---|---|
| Memory | Shared | Independent |
| Communication | Shared variables + Locks | Message Passing |
| Race Conditions | Yes | No |
| Data Synchronization | Manual locking required | No locks needed |
| Creation Overhead | Low | Medium (data must be copied) |
4. Isolate.run — A Simplified One-Shot Task
(1) The Simplest Isolate Usage
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
: Isolate.run Basics
import 'dart:isolate';
// Expensive computation to run in isolate
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Future<void> main() async {
print('Computing fibonacci(40) in isolate...');
final result = await Isolate.run(() => fibonacci(40));
print('Result: $result');
}
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
: Processing Data Chunks with Isolate.run
import 'dart:isolate';
// Process a chunk of orders
double processChunk(List<Map<String, dynamic>> orders) {
double total = 0;
for (final order in orders) {
total += (order['amount'] as double);
}
return total;
}
Future<void> main() async {
// Simulate 1M orders
final orders = List.generate(
1000000,
(i) => {'id': 'ORD-$i', 'amount': (i % 100 + 1) * 10.0},
);
// Split into 4 chunks
final chunkSize = orders.length ~/ 4;
final chunks = List.generate(
4,
(i) => orders.sublist(i * chunkSize, (i + 1) * chunkSize),
);
// Process chunks in parallel
final stopwatch = Stopwatch()..start();
final results = await Future.wait(
chunks.map((chunk) => Isolate.run(() => processChunk(chunk))),
);
stopwatch.stop();
final totalRevenue = results.fold(0.0, (a, b) => a + b);
print('Total revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
print('Time: ${stopwatch.elapsedMilliseconds}ms');
}
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
5. Isolate.spawn and Bidirectional Communication
(1) SendPort / ReceivePort Communication
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
: Bidirectional Communication
import 'dart:isolate';
// Worker function - runs in separate isolate
void workerIsolate(SendPort mainSendPort) {
final workerReceivePort = ReceivePort();
// Send our receive port to main isolate
mainSendPort.send(workerReceivePort.sendPort);
// Listen for messages from main isolate
workerReceivePort.listen((message) {
if (message is String && message == 'shutdown') {
workerReceivePort.close();
return;
}
// Process data and send result back
if (message is List<double>) {
final total = message.fold(0.0, (a, b) => a + b);
mainSendPort.send(total);
}
});
}
Future<void> main() async {
final mainReceivePort = ReceivePort();
// Spawn worker isolate
await Isolate.spawn(workerIsolate, mainReceivePort.sendPort);
// Get worker's send port
final workerSendPort = await mainReceivePort.first as SendPort;
// Create a new receive port for response
final responsePort = ReceivePort();
workerSendPort.send([1500.0, 3200.0, 890.0]);
// Wait for response
final result = await responsePort.first;
print('Result from worker: $result');
// Shutdown worker
workerSendPort.send('shutdown');
}
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
: Multi-round Communication
import 'dart:isolate';
void dataWorker(SendPort mainPort) {
final receivePort = ReceivePort();
mainPort.send(receivePort.sendPort);
receivePort.listen((message) {
if (message == 'done') {
receivePort.close();
return;
}
if (message is Map<String, dynamic>) {
// Process order data
final amount = message['amount'] as double;
final taxRate = message['taxRate'] as double? ?? 0.08;
final total = amount * (1 + taxRate);
mainPort.send({'id': message['id'], 'total': total});
}
});
}
Future<void> main() async {
final mainPort = ReceivePort();
await Isolate.spawn(dataWorker, mainPort.sendPort);
final workerPort = await mainPort.first as SendPort;
// Send multiple messages
final responsePort = ReceivePort();
workerPort.add({'id': 'ORD-001', 'amount': 1500.0, 'taxRate': 0.08});
workerPort.add({'id': 'ORD-002', 'amount': 3200.0});
// ... simplified communication
workerPort.send('done');
}
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
6. Isolates and Data Transfer
(1) Data Transfer Rules
| Transfer Method | Description | Performance |
|---|---|---|
| Primitive Types | int/double/String/bool | Fast copy |
| List/Map | Deep copy | Medium |
| Custom Objects | Deep copy | Medium |
| SendPort | Passed by reference | Fast |
| Function Closures | Passed via Isolate.run | Checked at compile time |
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
: Best Practice for Passing Large Data
import 'dart:isolate';
// Pass data via closure (Isolate.run)
Future<double> processLargeData(List<double> amounts) async {
return Isolate.run(() {
// amounts is copied into the isolate
double sum = 0;
for (final a in amounts) {
sum += a;
}
return sum;
});
}
// Better: pass only what's needed
Future<double> processChunkOptimized(List<double> chunk) async {
return Isolate.run(() => chunk.fold(0.0, (a, b) => a + b));
}
void main() async {
final data = List.generate(1000000, (i) => (i + 1) * 1.0);
// Split and process in parallel
final chunkSize = data.length ~/ 4;
final futures = List.generate(4, (i) {
final chunk = data.sublist(i * chunkSize, (i + 1) * chunkSize);
return Isolate.run(() => chunk.fold(0.0, (a, b) => a + b));
});
final results = await Future.wait(futures);
final total = results.fold(0.0, (a, b) => a + b);
print('Total: $total');
}
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
7. Bob's Scenario: Parallel Processing of a Million Orders
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
: DataPipeline Parallel Statistics
import 'dart:isolate';
class ChunkResult {
final int processed;
final int skipped;
final double revenue;
final Map<String, double> categoryRevenue;
ChunkResult({
required this.processed,
required this.skipped,
required this.revenue,
required this.categoryRevenue,
});
}
ChunkResult processChunk(List<Map<String, dynamic>> chunk) {
int processed = 0;
int skipped = 0;
double revenue = 0;
final categoryRevenue = <String, double>{};
for (final order in chunk) {
final amount = order['amount'] as double;
final status = order['status'] as String;
final category = order['category'] as String;
if (amount <= 0 || status == 'cancelled') {
skipped++;
continue;
}
processed++;
revenue += amount;
categoryRevenue.update(category, (v) => v + amount, ifAbsent: () => amount);
}
return ChunkResult(
processed: processed,
skipped: skipped,
revenue: revenue,
categoryRevenue: categoryRevenue,
);
}
Future<void> main() async {
// Generate 1.2M orders
final categories = ['Electronics', 'Books', 'Clothing', 'Home', 'Sports'];
final statuses = ['completed', 'completed', 'completed', 'pending', 'cancelled'];
final orders = List.generate(1200000, (i) => {
'id': 'ORD-${i.toString().padLeft(6, '0')}',
'amount': (i % 500 + 10) * 1.0,
'status': statuses[i % statuses.length],
'category': categories[i % categories.length],
});
// Split into chunks
final isolateCount = 4;
final chunkSize = orders.length ~/ isolateCount;
final chunks = List.generate(isolateCount, (i) {
final start = i * chunkSize;
final end = i == isolateCount - 1 ? orders.length : (i + 1) * chunkSize;
return orders.sublist(start, end);
});
// Process in parallel
final stopwatch = Stopwatch()..start();
final results = await Future.wait(
chunks.map((chunk) => Isolate.run(() => processChunk(chunk))),
);
stopwatch.stop();
// Aggregate results
int totalProcessed = 0;
int totalSkipped = 0;
double totalRevenue = 0;
final totalCategoryRevenue = <String, double>{};
for (final r in results) {
totalProcessed += r.processed;
totalSkipped += r.skipped;
totalRevenue += r.revenue;
for (final entry in r.categoryRevenue.entries) {
totalCategoryRevenue.update(entry.key, (v) => v + entry.value, ifAbsent: () => entry.value);
}
}
print('=== DataPipeline Parallel Report ===');
print('Orders: ${orders.length}');
print('Processed: $totalProcessed');
print('Skipped: $totalSkipped');
print('Revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
print('Time: ${stopwatch.elapsedMilliseconds}ms');
print('Isolates: $isolateCount');
print('\nBy Category:');
for (final entry in totalCategoryRevenue.entries.toList()..sort((a, b) => b.value.compareTo(a.value))) {
print(' ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
}
}
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
8. Complete Example: DataPipeline Isolate Parallel Processing Framework
// ============================================
// DataPipeline Isolate Parallel Processing
// Framework for parallel data analytics
// ============================================
import 'dart:isolate';
// Worker result
class WorkerResult {
final int workerId;
final int processed;
final int skipped;
final double revenue;
final Map<String, double> categoryRevenue;
final Duration processingTime;
WorkerResult({
required this.workerId,
required this.processed,
required this.skipped,
required this.revenue,
required this.categoryRevenue,
required this.processingTime,
});
}
// Worker function - runs in separate isolate
WorkerResult processInIsolate((int, List<Map<String, dynamic>>) input) {
final (workerId, orders) = input;
final stopwatch = Stopwatch()..start();
int processed = 0;
int skipped = 0;
double revenue = 0;
final categoryRevenue = <String, double>{};
for (final order in orders) {
final amount = (order['amount'] as num).toDouble();
final status = order['status'] as String;
final category = order['category'] as String;
if (amount <= 0 || status == 'cancelled') {
skipped++;
continue;
}
processed++;
revenue += amount;
categoryRevenue.update(category, (v) => v + amount, ifAbsent: () => amount);
}
stopwatch.stop();
return WorkerResult(
workerId: workerId,
processed: processed,
skipped: skipped,
revenue: revenue,
categoryRevenue: categoryRevenue,
processingTime: stopwatch.elapsed,
);
}
// Parallel pipeline manager
class ParallelPipeline {
final int isolateCount;
ParallelPipeline({this.isolateCount = 4});
Future<void> process(List<Map<String, dynamic>> orders) async {
print('=== DataPipeline Parallel Processing ===');
print('Orders: ${orders.length}, Isolates: $isolateCount');
// Split orders into chunks
final chunkSize = orders.length ~/ isolateCount;
final chunks = List.generate(isolateCount, (i) {
final start = i * chunkSize;
final end = i == isolateCount - 1 ? orders.length : (i + 1) * chunkSize;
return (i, orders.sublist(start, end));
});
// Process in parallel
final totalStopwatch = Stopwatch()..start();
final results = await Future.wait(
chunks.map((chunk) => Isolate.run(() => processInIsolate(chunk))),
);
totalStopwatch.stop();
// Aggregate
int totalProcessed = 0;
int totalSkipped = 0;
double totalRevenue = 0;
final totalCategoryRevenue = <String, double>{};
print('\nWorker Results:');
for (final r in results) {
totalProcessed += r.processed;
totalSkipped += r.skipped;
totalRevenue += r.revenue;
for (final entry in r.categoryRevenue.entries) {
totalCategoryRevenue.update(
entry.key, (v) => v + entry.value, ifAbsent: () => entry.value);
}
print(' Worker ${r.workerId}: ${r.processed} processed, '
'${r.processingTime.inMilliseconds}ms');
}
print('\n--- Aggregate Report ---');
print('Processed: $totalProcessed orders');
print('Skipped: $totalSkipped records');
print('Revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
print('Wall time: ${totalStopwatch.elapsedMilliseconds}ms');
print('\nBy Category:');
final sorted = totalCategoryRevenue.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
for (final entry in sorted) {
final pct = (entry.value / totalRevenue * 100).toStringAsFixed(1);
print(' ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD ($pct%)');
}
}
}
void main() async {
// Generate sample data
final categories = ['Electronics', 'Books', 'Clothing', 'Home', 'Sports'];
final statuses = ['completed', 'completed', 'completed', 'pending', 'cancelled'];
final orders = List.generate(500000, (i) => <String, dynamic>{
'id': 'ORD-${i.toString().padLeft(6, '0')}',
'amount': (i % 500 + 10) * 1.0,
'status': statuses[i % statuses.length],
'category': categories[i % categories.length],
});
final pipeline = ParallelPipeline(isolateCount: 4);
await pipeline.process(orders);
}
> **Output:** Run in local DartPad or with `dart run`. All Dart course examples are based on Dart 3.x / Flutter 3.x. Results may vary slightly with different SDK versions.
❓ FAQ
Q: What's the difference between an Isolate and a Thread? A: An Isolate has its own independent heap memory and does not share data; Threads share heap memory. Isolates communicate via message passing, while Threads communicate via shared variables + locks. Isolates have no race conditions.
Q: Is creating an Isolate expensive? A: Creating an Isolate takes about 50-150ms, which is slower than creating a thread. Frequent creation and destruction is inefficient; it's recommended to use an Isolate Pool or Isolate.run (which automatically manages the lifecycle).
Q: What's the difference between Isolate.run and Isolate.spawn? A: Isolate.run is a simplified API that executes a one-shot task and then automatically closes the Isolate. Isolate.spawn creates a persistent Isolate, requiring manual lifecycle and communication management.
Q: Is data copied when transferred between Isolates? A: Yes. All transferred data is deep-copied (except for SendPort). Passing large lists has performance overhead. For very large data, consider transferring in chunks.
Q: How many types of Isolates does Dart have? A: Mainly two: the general-purpose Isolate for parallelism and Flutter's compute for UI isolation. The web platform doesn't support true Isolates and uses Web Workers as a simulation.
Q: Is there an upper limit on the number of Isolates? A: There's no hard limit, but each Isolate occupies about 2MB of memory. In practice, it's recommended not to exceed the number of CPU cores to avoid excessive context switching.
Q: What scenarios are Isolates suitable for? A: CPU-intensive computations (data analysis, image processing, cryptographic calculations). For I/O-intensive tasks, Future/Stream is sufficient and Isolates are not needed.
📖 Summary
- Isolates are Dart's parallel units with independent memory, message-passing communication, and are naturally race-free
- Isolate.run is suitable for one-shot computation tasks and automatically manages the lifecycle
- Isolate.spawn + SendPort/ReceivePort is suitable for persistent communication
- Data transfers involve copying; for large data, transfer in chunks
- 4 Isolates processing a million orders in parallel reduces time from 15 seconds to 4 seconds
📝 Exercises
- Basic (⭐): Use
Isolate.runto calculate the 42nd Fibonacci number and compare the time with synchronous calculation in the main thread. - Intermediate (⭐⭐): Generate 1,000,000 random numbers, split them into 4 parts, and use 4 Isolates to calculate the sum and average of each part in parallel. Finally, aggregate the results in the main Isolate.
- Challenge (⭐⭐⭐): Implement an Isolate Pool using
Isolate.spawn: Pre-create N Worker Isolates, have the main Isolate distribute tasks via SendPort, and have Workers return results after processing. Support a task queue and load balancing.