skip to content

In a Flutter web build, what does compute() actually do, and why can a heavy parse still freeze the page?

level: middleimportance: should knowfreq 30%

answer

  1. no isolates in Dart on the web
  2. await null, then the callback
  3. same event loop as the UI
  4. portable API, no parallelism
  5. chunk it or shrink it

basics

~20 s

On the web, compute() yields once and then runs the callback directly on the same event loop, because Dart web apps have no isolates. The API stays portable, but a long parse still blocks the page.

solid answer

~40 s

Flutter picks `compute`'s implementation per platform. On Android, iOS and desktop it spawns an isolate. In a web build the implementation does `await null;`, which only defers by a microtask and does not wait for a frame, and then simply calls `callback(message)` on the **same event loop** that renders the page, because Dart on the web does not support isolates. So code using `compute` compiles and runs everywhere, but on the web it moves nothing off the main thread: a two-second parse is still a two-second freeze. Web-specific fixes are to make the payload smaller or pre-processed on the server, split the work into chunks that yield between them, or check `kIsWeb` and choose a different path. `BackgroundIsolateBinaryMessenger` is also unsupported there and throws.

code

dart · 14 lines
dart
import 'dart:convert';

import 'package:flutter/foundation.dart';

List<Object?> decodeSchedule(String body) => jsonDecode(body) as List<Object?>;

Future<List<Object?>> loadForPlatform(String body) {
  if (kIsWeb) {
    // compute() would run on the same event loop here anyway;
    // prefer a smaller, pre-grouped payload from the server on the web.
    return Future<List<Object?>>.value(decodeSchedule(body));
  }
  return compute(decodeSchedule, body);
}

go deeper

for a junior

Remember that compute() does not use a background isolate on the web, so heavy work there still freezes the page.

for a middle

Explain the conditional implementation: Isolate.run on native, await null then the callback on the web, and what that means for frames.

for a senior

Plan web-specific mitigations such as smaller payloads, chunking or lazy loading, and profile the web build on its own.

for a principal

Weigh whether heavy client-side processing belongs in a web target at all, or whether the backend should serve data shaped per platform.

## One API, two implementations `compute` is declared in `package:flutter/foundation.dart`, but its body comes from a **conditional import**: one implementation for platforms with `dart:io` and one for the web. - **Native (Android, iOS, macOS, Windows, Linux):** `compute(callback, message)` calls `Isolate.run`, which spawns a separate isolate, runs the callback there and returns the result. - **Web:** the implementation is essentially two lines: `await null;` and then `return callback(message);`. The `compute` documentation states it plainly: "On web platforms this will run callback on the current eventloop. On native platforms this will run callback in a separate isolate." The Flutter concurrency page adds that Dart web platforms, including Flutter web, don't support isolates, and that `compute` exists there so your code still compiles. ## Why the page still freezes The `await null` only postpones the callback by a microtask; the source comment says it lets the framework complete its current set of work, but it does not wait for a new frame, so a loading indicator set up just before the call may not even appear. After that, the callback runs synchronously on the **same event loop** that handles input and produces frames. A parse that takes two seconds occupies that loop for two seconds: - animations stop, - clicks and scrolls queue up, - the browser may report the page as unresponsive for long enough jobs. Nothing about `compute` changes this on the web; it is there for portability, not parallelism. ## What to do instead on the web | Approach | How it helps | Trade-off | |---|---|---| | Shrink or pre-process the payload on the server | Less work reaches the browser at all | Needs backend changes | | Split the work into chunks that yield between them | Frames can render between chunks | Total time grows; code gets more complex | | Load data lazily, per screen or per page | Only a small parse at a time | More requests | | Branch with `kIsWeb` for a different strategy | Keeps native builds on `compute` | Two code paths to test | Keep in mind that the web build compiles Dart to JavaScript or WebAssembly and runs it on the browser's main thread; browser-level workers are a separate mechanism outside `compute`'s scope. ## Related web limits - **`BackgroundIsolateBinaryMessenger`** has a web stand-in whose `ensureInitialized` and `instance` both throw `UnsupportedError('Isolates not supported on web.')`. - **Timing tests:** a test that proves `compute` removed jank on Android proves nothing about the web build. Profile the web build separately. ## The interview point A candidate who says "I used `compute`, so the web version is smooth" has missed the platform split. The strong answer names the web implementation, explains that it shares the event loop, and proposes a web-appropriate fix such as a smaller payload or chunked processing.

  • Why does compute() on the web start with await null?
    It defers the callback by one microtask so the framework can complete the synchronous work already in progress, as the source comment puts it. It does not wait for a frame, so it neither guarantees a visible loading indicator nor makes the callback any less blocking.
  • Does a Flutter web app need different tests for this?
    Yes. Performance proved on a native build says nothing about the web build, where `compute` shares the event loop. Profile the web build in the browser separately and budget payload sizes for it.

saying these in an interview costs you the question

  • compute() spawns a Web Worker automatically on the web.
  • Using compute() guarantees a smooth UI on every platform.
  • compute() fails to compile for the web, so web builds must avoid it.
  • BackgroundIsolateBinaryMessenger lets web plugins run in the background.