Web workers: moving computing load off the main thread
In short: A web worker runs JavaScript in its own thread so that heavy computations do not freeze the interface; communication happens via messages.
In more detail: JavaScript in the browser normally runs in the main thread, which also handles rendering and input. A long loop (processing an image, parsing big JSON, building a search index) makes the page stutter/freeze and worsens INP. A worker helps.
In Depth
Dedicated worker
// main.js
const worker = new Worker(new URL('./compute.js', import.meta.url), { type: 'module' });
worker.postMessage({ numbers: bigList });
worker.onmessage = (e) => show(e.data.sum);
// compute.js
self.onmessage = (e) => {
const sum = e.data.numbers.reduce((a, b) => a + b, 0);
self.postMessage({ sum });
};The worker has no access to the DOM and window, but to fetch, IndexedDB, WebAssembly, crypto, timers. Data is copied by structured clone; large buffers as transferables (postMessage(buf, [buf])) are moved instead of copied, or shared via SharedArrayBuffer (needs cross-origin isolation).
Types
- Dedicated worker: belongs to one page.
- Shared worker: used by several tabs.
- Service worker: network proxy/offline/push (its own task).
- Worklets (audio/paint) for special rendering tasks.
- OffscreenCanvas: drawing in the worker (games/graphics).
Libraries: Comlink turns worker calls into normal
asyncfunctions; Vite/Webpack bundle worker files.
When is it worth it?
Worth it: image/audio/video processing, parsing/compression, cryptography, WASM programs (Python in Pyodide, emulators), search/index, physics calculation in games, syntax analysis in editors. Not worth it: small stuff (cost of startup and copying), DOM work (impossible in a worker). Alternatively split long work into chunks (requestIdleCallback, scheduler.yield()) so the main thread can breathe.
Practice
- Catch errors (
worker.onerror), cancel (worker.terminate()), progress via messages. - Adapt the number of workers to cores (
navigator.hardwareConcurrency), reuse a pool. - Security: worker code same origin, mind CSP
worker-src; validate messages. - Debugging: DevTools show workers as their own threads.
- Node.js has
worker_threads, Python hasmultiprocessing(same idea: parallelism without shared state).
See also: Service worker, Event loop, Lighthouse and Core Web Vitals