Suggest an editImprove this articleRefine the answer for “What are web workers?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Web Workers** run JavaScript in a background thread, isolated from the main thread, so heavy computations don't freeze the UI. ```javascript const worker = new Worker('worker.js'); worker.postMessage(21); worker.onmessage = (e) => console.log(e.data); // 42 // worker.js: self.onmessage = (e) => self.postMessage(e.data * 2); ``` **Key point:** Workers have no DOM access; data passes via `postMessage` with deep-copy, not shared memory.Shown above the full answer for quick recall.Answer (EN)Image**Web Workers** let JavaScript run scripts in background threads, isolated from the main thread, so heavy computations don't freeze the UI. ## Theory ### TL;DR - The main thread is like a chef cooking orders one by one; Web Workers are kitchen assistants in a back room, passing finished plates via notes - No DOM access, no shared memory, pure message passing via `postMessage` / `onmessage` - Data is deep-copied using the structured clone algorithm, not shared between threads - Rule of thumb: if a computation blocks the UI for more than 50ms, it belongs in a Worker - Skip Workers for DOM updates, tasks under 10ms, or data that can't be cloned (functions, class instances) ### Quick example ```javascript // main.js const worker = new Worker('worker.js'); worker.postMessage(21); // send input worker.onmessage = (e) => console.log(e.data); // logs: 42 // worker.js self.onmessage = (e) => { const result = e.data * 2; // heavy math here won't freeze UI self.postMessage(result); // reply to main thread }; ``` The Worker runs `worker.js` in a separate thread. `postMessage` sends data, `onmessage` receives it on the other side. The main thread stays free the entire time. ### Isolation model A Web Worker spawns a fully isolated JavaScript context with its own global scope (`self`), no access to `window`, `document`, or the DOM. When you call `postMessage`, the browser uses the structured clone algorithm to deep-copy the data before handing it across the thread boundary. Both threads always get their own copy. That eliminates the shared-state bugs that make multithreaded code in other languages so painful. This is the one thing to internalize: there is no shared memory between main and Worker by default. You pass data, not references. ### When to use Use a Web Worker when: - A loop or computation takes more than 50ms and you see the page stutter - You're processing canvas pixel data for large images (4K+) - You're parsing JSON or CSV files larger than 1MB - You need cryptographic operations or ML inference in the browser Skip it when: - The task needs to read or write the DOM - The operation finishes in under 10ms - The data you'd send can't be cloned (functions, DOM nodes, objects with prototype methods) ### How browsers create workers Chrome (V8) and Firefox (SpiderMonkey) create a real OS thread per Worker via primitives like pthreads. The JS engine parses and compiles the worker script in that thread, fully isolated. Messages queue in a dedicated channel. The structured clone step blocks the sender momentarily during serialization, which is why sending 100MB ArrayBuffers synchronously is expensive. For large binary data, `SharedArrayBuffer` exists (requires `COOP`/`COEP` response headers), or you can transfer ownership with `postMessage(buffer, [buffer])` to avoid copying entirely. ### Common mistakes **Trying to touch the DOM:** ```javascript // worker.js - throws immediately document.getElementById('log').innerText = 'done'; // ReferenceError: document is not defined ``` There is no `document` in a Worker. Post a message back to main and let main update the DOM. **Sending non-cloneable data:** ```javascript // main.js - throws worker.postMessage({ fn: () => console.log('hi') }); // DataCloneError ``` Functions, DOM nodes, and class instances with prototype methods can't be cloned. Send primitives, plain objects, Arrays, Blobs, or ArrayBuffers. **Assuming variables are shared:** ```javascript let count = 0; // main.js worker.postMessage(count++); // worker sees 0, main increments to 1 - they're separate contexts ``` Isolated contexts mean no shared globals. Always pass the full state you need inside the message. **Forgetting to terminate:** Workers consume RAM until `terminate()` is called. This is the leak I see most in code reviews: a React component creates a Worker inside `useEffect` but returns nothing from that effect, so the Worker lives forever. Call `worker.terminate()` in the cleanup. A pool of 4 Workers is a standard production pattern; spawning a new one per task adds 50-200ms overhead each time. **Large message payloads:** Cloning a 100MB ArrayBuffer blocks both threads for 500ms or more. Chunk the data, or use transferable objects: `worker.postMessage(buffer, [buffer])` moves ownership without copying. ### Real-world usage - Figma runs layout calculations for prototype mode in Workers to keep the canvas at 60fps - Fabric.js uses Workers for SVG parsing in canvas editors - Three.js offloads WebGL shader compilation off-main to avoid frame drops - Photopea processes 4K image filters in Workers using OffscreenCanvas - Comlink (used at Vercel) wraps Workers as proxy objects so they feel like regular async function calls ### Follow-up questions **Q:** How do you pass data between the main thread and a Worker? **A:** `postMessage` sends data and `onmessage` receives it on the other side. Data is deep-copied via the structured clone algorithm, so changes on one thread don't affect the other. **Q:** What data types can you send through `postMessage`? **A:** Primitives, plain objects, Arrays, Blobs, ArrayBuffers, and ImageData. Functions, DOM nodes, and objects with Symbol keys throw a `DataCloneError`. **Q:** What is the difference between a Worker and a SharedWorker? **A:** A `SharedWorker` lets multiple tabs or scripts connect to the same worker instance by name, sharing one background thread. A regular Worker is owned by a single script. **Q:** How do you debug Web Workers? **A:** In Chrome DevTools, open Sources and look for the Worker icon in the left sidebar. Each Worker gets its own scope with breakpoints and a separate console. **Q:** (Senior) Why build a Worker pool instead of spawning one Worker per task? **A:** Spawning a Worker costs 50-200ms each time. A pool of 4-8 Workers amortizes that cost by reusing threads. Tasks queue into an array and get dispatched to the next free Worker via round-robin. Idle Workers terminate after 30 seconds to free memory. ## Examples ### Basic: double a number in the background ```javascript // worker.js self.onmessage = (e) => { const result = e.data * 2; self.postMessage(result); }; // main.js const worker = new Worker('worker.js'); worker.postMessage(21); worker.onmessage = (e) => console.log(e.data); // 42 worker.terminate(); // free resources ``` The simplest possible Worker: receive a number, return a number. Nothing blocks the main thread while the calculation runs. ### Intermediate: grayscale image filter with OffscreenCanvas ```javascript // ImageFilterWorker.js self.onmessage = async (e) => { const { imageData } = e.data; const canvas = new OffscreenCanvas(imageData.width, imageData.height); const ctx = canvas.getContext('2d'); ctx.putImageData(imageData, 0, 0); const out = ctx.getImageData(0, 0, canvas.width, canvas.height); for (let i = 0; i < out.data.length; i += 4) { // standard luminance formula const gray = 0.3 * out.data[i] + 0.59 * out.data[i + 1] + 0.11 * out.data[i + 2]; out.data[i] = out.data[i + 1] = out.data[i + 2] = gray; } ctx.putImageData(out, 0, 0); self.postMessage(await canvas.convertToBlob()); }; // React component const worker = new Worker('ImageFilterWorker.js'); worker.onmessage = (e) => setFilteredImage(URL.createObjectURL(e.data)); worker.postMessage({ imageData: ctx.getImageData(0, 0, width, height) }); ``` Processing a 4K image pixel-by-pixel in the main thread would freeze the UI for several seconds. In a Worker, the component stays interactive the whole time. `OffscreenCanvas` gives the Worker a full canvas API without any DOM dependency. ### Advanced: error handling and termination ```javascript // main.js const worker = new Worker('error-worker.js'); worker.onerror = (e) => { console.error('Worker crashed:', e.message); // "Intentional crash" worker.terminate(); }; worker.postMessage('crash'); // error-worker.js self.onmessage = (e) => { if (e.data === 'crash') throw new Error('Intentional crash'); self.postMessage('ok'); }; ``` Unhandled errors in a Worker fire `onerror` on the main thread. The main thread itself does not crash. But if you skip `onerror`, the error disappears silently. Always attach it. And note that `terminate()` kills the Worker instantly with no cleanup callback, so flush any state you need before calling it.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.