Summary
Web Workers run JavaScript on a separate OS thread, keeping CPU-intensive work — image processing, data parsing, wasm pipelines — off the rendering loop. `postMessage` copies data via structured clone or transfers `ArrayBuffer` ownership in O(1). `SharedArrayBuffer` enables shared memory but requires COOP/COEP headers to activate. Comlink wraps workers in a Promise-based RPC layer, removing postMessage boilerplate.
Jump to the interview angleA Web Worker is a script that runs on a background OS thread, separate from the main thread that handles rendering, event dispatch, and JavaScript execution. Because each thread has its own event loop, long tasks in a worker cannot block painting or input handling. Workers communicate with the main thread only through postMessage, which copies data via structured clone or transfers ownership of binary buffers. Workers have no access to the DOM.
Comlink worker — ergonomic RPC over postMessage
Comlink (Google Chrome Labs) wraps a worker class so callers await methods as if they were local async functions, eliminating manual postMessage/onmessage wiring.
// worker.ts
import { expose } from "comlink";
const api = {
async processChunk(data: Float32Array): Promise<number> {
let sum = 0;
for (let i = 0; i < data.length; i++) sum += data[i];
return sum;
},
};
expose(api);
// main.ts
import { wrap } from "comlink";
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
type: "module",
});
const remote = wrap<typeof api>(worker);
// Awaiting a worker method — no postMessage plumbing needed.
const result = await remote.processChunk(new Float32Array([1, 2, 3]));
console.log(result); // 6expose registers the object inside the worker; wrap returns a Proxy on the main thread. Each method call becomes a postMessage round-trip under the hood, typed end-to-end via TypeScript generics.
Transferring data across the thread boundary
| Method | Copy cost | Zero-copy | Shared writes | |
|---|---|---|---|---|
| Structured clone (default) | O(n) per call | No | No | |
| Transferable (ArrayBuffer) | O(1) | Yes — ownership moves | No | |
| SharedArrayBuffer + Atomics | O(1) | Yes — both see same memory | Yes |
SharedArrayBuffer requires COOP + COEP headers
Browsers disable SharedArrayBuffer unless the page is cross-origin isolated: send Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp on every document response. Without them SharedArrayBuffer is undefined at runtime, even in modern browsers. Verify with self.crossOriginIsolated === true before using it.
When a worker is worth the overhead
- Any task taking **>50 ms** on the main thread is a candidate — that's one dropped frame at 60 fps.
- Image or video processing, wasm modules, large JSON parse/stringify, and crypto hashing are natural fits.
- **Avoid** workers for small, infrequent tasks — thread startup (~5 ms), serialization, and proxy overhead add up.
- Use a **worker pool** (e.g. `workerpool`) to amortize startup across repeated calls.
- Workers cannot touch the DOM; pass back computed values and let the main thread apply them.
Interview angle
Interviewers ask why the main thread matters and how to offload work without blocking it. Strong answers name the structured-clone vs transferable distinction, mention Comlink for ergonomics, and explain the COOP/COEP requirement for SharedArrayBuffer.
Soundbite: "Transfer ArrayBuffer ownership in O(1) for data pipelines; reach for SharedArrayBuffer only when both threads write, and only once you've set COOP/COEP headers."
Key terms
- Structured clone
- Default postMessage serialization — deep-copies the value between threads, O(n) in data size.
- Transferable
- An object (e.g. `ArrayBuffer`) whose ownership moves to the receiver thread in O(1) with zero copy.
- SharedArrayBuffer
- A fixed-length binary buffer visible to both threads simultaneously; writes on one are visible on the other.
- Atomics
- Built-in API for lock-free, thread-safe operations on `SharedArrayBuffer` — `Atomics.wait`, `Atomics.notify`.
- Comlink
- Library that wraps a Worker with a Proxy, exposing its methods as awaitable async functions via postMessage RPC.