EMZETT.
Login

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 async functions; 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 has multiprocessing (same idea: parallelism without shared state).

See also: Service worker, Event loop, Lighthouse and Core Web Vitals