Concept
The beginner framing: a Web Worker runs a JavaScript file on a completely separate thread from your page, genuinely, physically parallel, not just "async" in the single-thread sense everything else in this course has covered. It's useful for heavy computation that would otherwise freeze the page.
The precise mental model: every other async mechanism in this course (setTimeout, promises, fetch) still runs on the one JavaScript thread, they're about scheduling, not parallelism (see Event Loop). A worker is different in kind: it's a real OS thread with its own JavaScript engine instance, own global scope, and own event loop, running your code at the same time as your main thread, not interleaved with it. This is why a worker can run a genuinely expensive computation without freezing the page's UI, the main thread's event loop is completely free to keep handling clicks and rendering while the worker crunches numbers elsewhere.
// main.js
const worker = new Worker("worker.js");
worker.postMessage({ command: "calculate", data: [1, 2, 3, 4, 5] });
worker.onmessage = (event) => {
console.log("result from worker:", event.data);
};// worker.js, runs on its OWN thread, own global scope (no `window`, no DOM)
self.onmessage = (event) => {
const { command, data } = event.data;
if (command === "calculate") {
const result = data.reduce((sum, n) => sum + n, 0);
self.postMessage(result);
}
};The critical constraint: NO shared memory
Workers cannot access the DOM, window, or any variable from the main thread directly, there is no shared memory between a worker and the thread that created it (with the narrow exception of SharedArrayBuffer, a special, opt-in low-level primitive most application code never touches). All communication happens exclusively through postMessage()/onmessage, and the data you send is copied, not shared, via an internal browser mechanism called the structured clone algorithm.
const worker = new Worker("worker.js");
const bigObject = { data: new Array(1_000_000).fill(0) };
worker.postMessage(bigObject);
// The ENTIRE object is deep-cloned and copied to the worker's memory space.
// Mutating `bigObject` on the main thread afterward has NO effect on the
// worker's copy, they are now two completely independent objects.This is fundamentally different from how objects normally behave in JavaScript (see Fundamentals, objects are usually passed by reference). Crossing the worker boundary is the one place in the language where "by reference" genuinely becomes "by value," because there is no shared address space to reference into.
Transferable objects: opting into a zero-copy handoff
For large binary data, cloning is wasteful, Transferable objects (like ArrayBuffer) can be transferred instead of copied, moving ownership to the worker instantly, at the cost of the original thread losing access to it entirely.
const buffer = new ArrayBuffer(1024 * 1024 * 32); // 32MB
worker.postMessage(buffer, [buffer]); // second argument: list of transferables
console.log(buffer.byteLength); // 0, the ORIGINAL thread no longer owns this memoryTry It
This code has a very common web worker misconception baked into it. Find it before checking the solution.