In Dart, why doesn't marking a CPU-heavy function async keep the program responsive, and what does running it in another isolate change?
answer
- async waits, it does not compute elsewhere
- body runs synchronously until await
- one event loop per isolate
- separate heap, no shared memory
- parallel on another core
basics
~20 sAn async function still runs on the calling isolate's single thread; its synchronous work blocks that isolate's event loop until the next await. Another isolate has its own heap and event loop and can run the work in parallel on another core.
solid answer
~50 s`async`/`await` lets one isolate interleave work while it *waits* — for a file, a socket, a timer. It does not move computation anywhere: an `async` function runs synchronously on the caller's isolate until its first `await`, so a loop hashing thousands of files blocks that isolate's event loop the whole time — no timers fire, no I/O callbacks run, and in Flutter no frames are produced. An **isolate** is a separate Dart execution context with its own heap, globals and event loop; the VM can run it on another core in parallel. Isolates share no mutable memory — they communicate by messages — so there are no locks or data races, but data must be sent across. Unlike threads in Java or Kotlin, which share one heap, an isolate cannot read the main isolate's objects directly.
code
dart · 14 linesimport 'dart:io';
import 'dart:isolate';
int checksum(List<int> bytes) =>
bytes.fold(0, (hash, b) => (hash * 31 + b) & 0xffffffff);
// Blocks the calling isolate for the whole loop despite `async`.
Future<Map<String, int>> hashAllBlocking(List<String> paths) async =>
{for (final p in paths) p: checksum(File(p).readAsBytesSync())};
// Runs the same loop in a worker isolate; the caller stays free.
Future<Map<String, int>> hashAllInWorker(List<String> paths) =>
Isolate.run(() =>
{for (final p in paths) p: checksum(File(p).readAsBytesSync())});go deeper
Say clearly that async only helps while waiting, and that heavy computation needs another isolate to avoid blocking.
Explain that an async body runs synchronously until its first pending await, and that isolates have separate heaps and communicate only by messages.
Decide between async and isolates from whether work is CPU-bound or I/O-bound, and account for spawn and transfer costs and the lack of isolates on the web.
Frame isolate use as an architectural boundary: which components own CPU-heavy work, what data crosses the boundary, and how that shapes APIs.
## What async actually does Every piece of Dart code runs inside an **isolate**: a thread of control with its own memory and a single **event loop** that processes one event at a time. `async` and `await` are tools for *waiting* without blocking that loop: - An `async` function runs **synchronously** from its first line until it reaches an `await` on a future that is not yet complete. - At that point it suspends, returns a `Future` to its caller, and the event loop is free to handle other events. - When the awaited future completes, the rest of the function is scheduled as another event on the **same** isolate. Nothing in that sequence runs code on another core. If the function body is a tight loop that computes — hashing, parsing, compressing, image processing — there is no `await` to yield at, and the whole computation occupies the isolate until it finishes. ## The symptom Consider a backup command-line tool that computes a checksum for every file before upload: ```dart Future<Map<String, int>> hashAll(List<String> paths) async { return {for (final p in paths) p: checksum(File(p).readAsBytesSync())}; } ``` The `async` keyword changes only the return type. For ten thousand files the loop runs start to finish in one go: the progress timer never ticks, a Ctrl-C handler registered with `ProcessSignal` waits, and in a Flutter app the same code would freeze animations because the UI isolate cannot build frames. ## What an isolate changes | Property | Same isolate with async | Worker isolate | |---|---|---| | Where the CPU work runs | The caller's thread, blocking its event loop | A separate isolate the VM can run on another core | | Memory | Shared with all code in the isolate | Its own heap and its own copy of every global | | Communication | Direct object access | Messages: arguments in, result out | | Data races | Impossible (one thread) | Impossible (no shared mutable state) | | Cost | Nothing extra | Spawn time, some memory, and moving data across | In practice: 1. **Parallelism.** With `Isolate.run` or `Isolate.spawn`, the checksum loop runs in another isolate while the main isolate keeps handling its events. On a multi-core machine several worker isolates hash files at the same time. 2. **Isolation.** A worker cannot touch the main isolate's objects. A global counter incremented in the worker stays unchanged in the main isolate, because each isolate has its own copy. 3. **Messaging.** Inputs travel with the spawn call and results come back as messages. Within one program this is cheap, but not free, and some objects — open files and sockets, for example — cannot be sent at all. ## Isolates versus threads Threads in languages such as Java or Kotlin share one heap: any thread can read and write any object, so correctness depends on locks and memory-visibility rules. Dart isolates trade that for **isolation**: there is no shared mutable state, so no locks and no data races, but also no reaching into another isolate's data. Isolates can still have *race conditions* at the level of message ordering; they just cannot corrupt each other's memory. ## When to reach for one - The work is **CPU-bound** and long enough to delay other events noticeably. - The inputs and outputs are data that can be sent between isolates. - You are on a **native** platform: the Dart web platform does not implement isolates. For I/O-bound work — many network calls, many file reads — plain `async` with `Future.wait` already overlaps the waiting, and an isolate adds cost without benefit.
- Would changing readAsBytesSync to await readAsBytes make the hashing loop responsive?Only partly. Each `await` lets the event loop run other events while a file is read, so timers and I/O callbacks get turns between files. The checksum of each file is still computed synchronously on the same isolate, so a large file still blocks for its whole hash, and no work runs in parallel.
- If a worker isolate increments a top-level counter, what does the main isolate see?Nothing. Each isolate has its own copy of every global and static field, initialised independently. The worker's increment changes only its own copy; to report a count, the worker must send it back as a message or return it.
A single cook who can put several pots on the stove and wait on all of them, but who still has to do every bit of chopping personally; an isolate is a second kitchen with its own cook, receiving orders and sending back finished plates through a hatch.
saying these in an interview costs you the question
- Marking a function async runs it on a background thread.
- An await-free async function yields to the event loop between iterations.
- Isolates share the heap, so a worker can update the caller's objects.
- Isolates need locks to protect shared data.
- An isolate always speeds up I/O-bound work like many HTTP calls.