Web Workers, SharedArrayBuffer and cross-origin isolation
Move heavy work off the main thread with Web Workers: postMessage and structured cloning, transferable objects, module workers, what workers cannot touch, RPC wrappers, and why SharedArrayBuffer needs COOP and COEP.
Comes up in: Web fundamentals, Frontend system design
Learn#
Fatima’s expense app freezes for 3 seconds when users upload a 40 MB bank statement. The CSV parser runs on the main thread, so clicks, typing and even the spinner stop (JS.01.07).
A web worker runs JavaScript on a separate thread. The page and the worker share no variables; they talk by sending messages with postMessage.
Messages are copied with the structured clone algorithm, the same one structuredClone() uses:
const order = { placed: new Date(0), tags: new Set(["gift"]), pixels: new ArrayBuffer(1024) };
const copy = structuredClone(order, { transfer: [order.pixels] });
console.log(copy.placed instanceof Date, copy.tags.has("gift"));
console.log("after transfer:", order.pixels.byteLength, "→", copy.pixels.byteLength);
try { structuredClone({ onSave() {} }); } catch (e) { console.log(e.name); }Output (verified)
true true after transfer: 0 → 1024 DataCloneError
Dates and Sets survive; functions cannot be sent.
A transferable, such as an ArrayBuffer, moves instead of copying: the sender’s copy becomes empty, so big data costs nothing to send.
State
- main thread: UI
- worker thread: idle
Console
empty
The page creates a worker from parse-worker.js. It runs on its own thread, with its own global scope.
Read the steps as text
- The page creates a worker from parse-worker.js. It runs on its own thread, with its own global scope.
- The page sends the uploaded file's ArrayBuffer and lists it as transferable. No copy is made.
- The worker parses 400,000 CSV rows for 3 seconds. The main thread stays free: scrolling and typing still work.
- The worker posts back a small summary. The main thread receives it in a message event and renders it.
Predict#
Predict first
Inside a dedicated worker, you call document.querySelector('#total'). What happens?
Workers have no DOM: no document, no window. They can use fetch, timers, IndexedDB and OffscreenCanvas. The page must update the DOM with the worker’s results. People expect a worker to be “the same page, in the background”.
Interview angle#
Say it like this — about 30 seconds
“A Web Worker runs JavaScript on its own thread, so heavy work such as parsing or image processing does not block input. It cannot touch the DOM. The page and the worker talk with postMessage, which copies data using structured clone, so big objects cost time; transferable objects such as ArrayBuffer move instead, leaving the sender’s copy empty. SharedArrayBuffer shares memory between threads, but since Spectre it works only on cross-origin isolated pages, which need COOP same-origin and COEP headers.”
Follow-up What is a Web Worker, and what can it not do?
Follow-up What are transferable objects?
Follow-up Why does SharedArrayBuffer need cross-origin isolation?
Practice#
On your own
Write a small RPC layer over a MessagePort, the idea behind Comlink, so calling a worker feels like calling an async function. The tests use a real MessageChannel.
Done when
wrap(port).sum(2, 3)resolves to what the exposedsumreturns, also for async methods.- Errors thrown in the exposed method reject the caller’s promise with the same message.
- Several calls in flight at once each get their own answer.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
What can a web worker not access?
The DOM: no document or window.
How does postMessage copy data?
With the structured clone algorithm.
What happens to an ArrayBuffer after you transfer it?
It moves; the sender's copy becomes empty (byteLength 0).
Which two headers make a page cross-origin isolated?
COOP: same-origin and COEP: require-corp (or credentialless).
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
new Worker(new URL("./parse.js", import.meta.url), { type: "module" }) starts a worker that can use import: Chrome 80, Firefox 114, Safari 15.fetch, timers, IndexedDB, WebSockets and OffscreenCanvas, but no DOM.Check yourself: How do you start a module worker?
new Worker(url, { type: 'module' })
How it works underneathSenior rounds
Check yourself: Why match replies by id in a worker RPC?
Several calls can be in flight, and replies may come back in any order.
Edge cases and trapsWhere people slip
same-origin separates your window from cross-origin popups. COEP require-corp loads only cross-origin resources that opt in.self.crossOriginIsolated Source: web.dev: Making your website cross-origin isolated using COOP and COEP.Check yourself: What does COEP: require-corp make the page do?
Load cross-origin resources only if they opt in with CORS or a CORP header.
In productionStaff-level answers
Atomics gives safe reads and writes, and Atomics.wait and notify let threads sleep and wake; the main thread may not block, so it uses Atomics.waitAsync.Check yourself: What does Atomics.wait do, and where is it not allowed?
Blocks a thread until notified; not allowed on the main thread.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- What is a Web Worker and what can it not do?
- What are transferable objects?
- Why does SharedArrayBuffer need cross-origin isolation?