Back to News & Insights
JavaScript September 7, 2026 · 10 min read

Web Workers in JavaScript: The Complete Guide

Learn JavaScript Web Workers step by step: message passing, transferable objects, error handling, and a copy-paste cheat sheet for offloading work.

Web Workers in JavaScript: The Complete Guide

Drag a slider on a page that's mid-way through parsing a 50,000-row CSV in JavaScript, and nothing happens for a full second. Not because the slider's code is slow — it never even runs. The browser's one JavaScript thread is busy with your parsing loop, and until that loop returns, no click, no scroll, no repaint gets a turn. The fix already ships in every browser you support: a second thread, called a Web Worker, that runs your code without ever touching the one thread the page's UI depends on.

By the end of this guide you'll be able to: Explain why the browser has exactly one thread for JavaScript and the DOM, and why that thread stalls the whole page under heavy work Create a Web Worker, send it data, and get a result back without blocking the main thread Reason correctly about what does and doesn't survive the trip between threads (structured cloning vs. transferable objects) Handle worker errors, terminate workers cleanly, and avoid the memory leaks that come from forgetting to Decide, with real judgment, when a worker is worth the complexity and when it isn't

Who this is for: you've written addEventListener handlers and used fetch, and you've felt a UI stutter you couldn't explain.

Contents Why Web Workers exist The mental model Building a worker, stage by stage Edge cases and gotchas Best practices FAQ Cheat sheet Key takeaways

JavaScript in the browser runs on a single thread — the main thread — and that same thread is also responsible for parsing HTML, computing styles, laying out and painting pixels, and responding to input. When your code runs, everything else waits. Here's the naïve version of the CSV problem from the opening line:

While sumColumn runs, the browser cannot repaint, cannot fire scroll or click handlers, and cannot update anything on screen — including a "loading" spinner you might have shown a moment earlier. The tab looks frozen because, for that stretch of time, it is. setTimeout(fn, 0) doesn't help either: it still runs the callback on the same main thread, just slightly later; it defers the freeze, it doesn't remove it.

A Web Worker solves this by giving that expensive loop its own thread, with its own JavaScript engine instance, running in parallel with the main thread. The main thread stays free to paint and respond to input the entire time.

The mental model: a Web Worker is a separate JavaScript environment, running in parallel, that shares no memory with the page — the only way in or out is sending copies of data through a message channel.

Picture two rooms with no shared furniture and no window between them, connected by a mail slot. You can pass a note through the slot (postMessage), and the other room can read it and mail one back (onmessage). Neither room can reach into the other and grab a variable, call a function, or touch a DOM element sitting in the other room — because nothing is actually shared. What crosses the slot is a copy of the data, produced by an algorithm called structured cloning, not a reference to the original.

That single fact — no shared memory, only copied messages — explains almost every rule that follows: why a worker can't touch the DOM (the DOM objects live in the main thread's room), why you can't pass a function to a worker (functions aren't cloneable), and why very large payloads need a different trick (transferable objects, covered below).

Key concept: the worker file has no access to window or the DOM — inside it, self refers to the worker's own global scope (DedicatedWorkerGlobalScope), not the page.

Clicking the button now posts the data to the worker and returns immediately; the main thread never blocks. The heavy loop runs on the worker's thread, and the page keeps painting and responding to input the whole time.

You don't always want a second network request for a small worker script. You can build one from a string using a Blob and an object URL:

This is how the playground below builds its worker inline — useful for demos, small utility workers, or libraries that want to ship a worker without a second file to deploy.

Classic workers load dependencies with the older importScripts() function. Modern browsers also support module workers, which use standard import statements, by passing { type: "module" }:

Module workers are supported in all current major browsers as of 2026. If you need to support an environment that predates module worker support, stick with a classic worker and importScripts().

Structured cloning copies data. For a plain object with a few numbers, that copy is instant. For a 200 MB ArrayBuffer of audio or image data, copying it on every message becomes the new bottleneck. The fix is a transferable object: instead of copying an ArrayBuffer, you transfer ownership of it to the worker.

Key concept: transferring moves the underlying memory instead of copying it, which is why it's effectively free even for huge buffers — and why the original reference becomes unusable afterward.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation