A web worker is a JavaScript file that runs on its own thread, completely separate from the thread running your page. That separation is the entire point: a heavy computation — parsing a huge JSON file, resizing images, running a search index, hashing data — can run inside a worker without freezing scrolling, typing, or animation on the page. The tradeoff is that a worker lives in an isolated world. It cannot see the DOM, it cannot share variables with your page directly, and every piece of data crossing the boundary has to be explicitly sent as a message.
What changed in 2026
- Module workers are now the default way to write one. Creating a worker with
type: "module" lets the file use import statements directly, instead of the older importScripts function.
- Cross-origin isolation is more commonly configured, unlocking
SharedArrayBuffer and high-resolution timers for workers that need true shared-memory parallelism, such as WebAssembly threads.
- Client-side inference workloads pushed worker usage up. Running a small ML model or embedding computation in the browser is increasingly done inside a worker so the UI stays responsive.
- RPC-style wrapper libraries are now the common pattern. Rather than writing raw postMessage handlers, most teams wrap workers with a small library that makes calling one look like a normal async function call.
How a web worker works
The page creates the worker and sends it data. The worker computes and sends a result back. Neither side ever touches the other's variables directly.
// main.js
const worker = new Worker("sort-worker.js");
worker.postMessage({ data: largeArray });
worker.onmessage = (event) => {
console.log("Sorted:", event.data);
};
// sort-worker.js
self.onmessage = (event) => {
const sorted = event.data.data.sort((a, b) => a - b);
self.postMessage(sorted);
};
Wrapping this exchange in a promise, the same return-and-settle pattern used in a promise chain, turns postMessage into something that reads like a normal async function call instead of a pair of event handlers.
What a worker can and cannot do
A worker can run plain JavaScript computation, use fetch, timers, and most Web APIs that do not depend on the page. It cannot access window, document, or any UI element, and cannot share memory with the main thread unless you opt into SharedArrayBuffer.
Worker types compared
| Type |
Runs when |
Purpose |
Lifetime |
| Web worker (dedicated) |
While the page that created it is open |
Offload computation from the main thread |
Tied to that one page |
| Shared worker |
While any connected tab is open |
Share one worker across multiple tabs or windows |
Tied to all connected tabs |
| Service worker |
Even when no tab is open |
Intercept network requests, cache assets, enable offline and push |
Independent of any single page |
A progressive web app typically relies on a service worker for offline support, not a dedicated web worker — the two are easy to confuse by name but solve different problems.
When to actually use one
Reach for a worker when the task is measurably heavy: parsing a large dataset, image or video processing, client-side encryption or hashing, search indexing, or syntax highlighting across a large file. For anything that finishes in a few milliseconds, the overhead of creating the worker and messaging back and forth costs more than it saves.
Common mistakes
Passing large binary data by copy instead of transferring it. A plain postMessage clones the data by default. For large ArrayBuffer objects, pass it as a transferable object so the memory is handed off instead of duplicated.
Expecting DOM access inside the worker. Any attempt to reach document or window from inside a worker throws immediately; it has to compute a result and message it back for the page to apply.
Creating a new worker per task instead of reusing one. Spinning up a worker has real startup cost. For repeated tasks, keep one long-lived worker running and send it messages as work arrives.
Never terminating workers that are no longer needed. Call worker.terminate() when the work is done, or the thread and its memory stay alive longer than necessary.
FAQ
Can a web worker access the DOM?
No. Workers run in a separate global scope with no access to window, document, or any DOM API. They can only compute and send results back through messages.
Does every browser support web workers?
Yes, dedicated web workers have been supported in every major browser for years. Support for newer additions like module workers and SharedArrayBuffer varies more, so check compatibility before depending on them.
How is a web worker different from a service worker?
A web worker offloads computation for the page that created it. A service worker sits between the page and the network, intercepting requests, caching responses, and enabling offline support and push notifications, even when no tab is open.
Is passing data to a worker slow?
It depends on size and method. Small objects clone quickly. Large binary data should use a transferable object so the memory is handed off rather than copied, which stays fast regardless of size.
Where to go next