Web Workers
A Web Worker is a script that runs on a separate background thread , communicating with the main thread only by passing messages, so heavy computation can run without freezing the user interface.
Learn Web Workers in our free JavaScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
They are the answer to the spinning beachball: when a calculation would lock up the page for seconds, you move it to a worker and the UI keeps scrolling, animating, and responding.
📚 Prerequisites: Helpful to understand the event loop and callbacks , since worker messaging is event-driven.
💡 About the runnable demos: The real Worker API needs a browser, so it lives in the read-only code blocks below. Each Try it Yourself instead runs a plain-JavaScript model of the same idea (a tiny message bus and a CPU task) so you can see the exact request → compute → reply flow in your console.
👨🍳 Real-World Analogy: The main thread is a waiter taking orders. If the waiter also has to cook a three-hour roast, every other customer waits and the restaurant grinds to a halt. A Web Worker is the kitchen : the waiter hands an order through the window ( postMessage ), keeps serving everyone else, and picks up the finished plate when the kitchen rings the bell ( onmessage ).
JavaScript runs your code on a single main thread. A long synchronous loop blocks everything — clicks, animations, even scrolling. A Web Worker runs a separate script file on its own thread. You create one with new Worker(url) , send it work with postMessage , and the main thread is free the entire time.
⚠️ Different file, different world: A worker has no access to window , document , or the DOM. It is for computation only — send results back and let the main thread update the page.
The demo below models the same flow with a tiny in-process message bus, so you can watch a request go out and a computed reply come back:
Messages are not shared by reference — they are deep-copied with the structured clone algorithm. The worker gets its own copy, so mutating it on one side never affects the other. Structured clone handles objects, arrays, Map , Set , Date , and typed arrays — but not functions or DOM nodes.
Node ships the same algorithm as the global structuredClone() , so you can prove the copy-not-share behaviour right here:
Replace the blank with the method that sends data into the worker model.
A worker keeps running until you stop it. Call worker.terminate() from the main thread (or self.close() inside the worker) to shut it down and free its thread. Use a worker for sustained CPU-bound work — parsing big files, image filters, cryptography. Skip it for quick tasks or I/O-bound waiting, where the messaging and copying overhead is not worth it.
The model below adds a job queue and a "terminate" flag so you can see a worker refuse work after shutdown:
The reply arrives as an event. Replace the blank with the property that holds the payload.
No — workers have no DOM access. Send the result back and let the main thread update the page.
Throws a DataCloneError — functions cannot be structured-cloned.
The prime sieve — it is CPU-bound. The fetch already runs off-thread; async/await handles it.
Real apps send many jobs and need to match each reply to its request. Add an id to each message and route the reply back through a pending-jobs map — the foundation of a worker RPC layer.
Up next: IntersectionObserver — react when elements scroll into view. 👀
Practice quiz
What is a Web Worker?
- A faster version of setTimeout
- A CSS animation tool
- A script that runs on a separate background thread
- A type of network request
Answer: A script that runs on a separate background thread. A Web Worker is a script that runs on a separate background thread, so heavy computation does not freeze the UI.
Which kind of work is a Web Worker best suited for?
- Sustained CPU-bound work like parsing big files or image filters
- I/O-bound waiting like awaiting a fetch
- Updating the DOM
- Reading cookies
Answer: Sustained CPU-bound work like parsing big files or image filters. Workers shine for sustained CPU-bound work; async/await already handles I/O-bound waiting off the main thread.
How do the main thread and a worker communicate?
- By sharing variables directly
- Through the DOM
- Via localStorage only
- By passing messages with postMessage and onmessage
Answer: By passing messages with postMessage and onmessage. They never share variables; they pass messages — postMessage to send, onmessage to receive.
How do you create a worker in the real browser API?
- createWorker(url)
- new Worker(url)
- Worker.start(url)
- spawn(url)
Answer: new Worker(url). You create a worker with new Worker(url), pointing at a separate script file.
When you postMessage an object, how is it transferred?
- Deep-copied using the structured clone algorithm
- Shared by reference
- Converted to a string only
- Not transferred at all
Answer: Deep-copied using the structured clone algorithm. Data is deep-copied with the structured clone algorithm, so each side gets its own independent copy.
What happens if you try to postMessage a function?
- It works fine
- It silently becomes null
- It throws a DataCloneError because functions can't be cloned
- It runs the function immediately
Answer: It throws a DataCloneError because functions can't be cloned. Functions (and DOM nodes) cannot be structured-cloned, so posting them throws a DataCloneError.
Can a Web Worker access the DOM (e.g., document.getElementById)?
- Yes, fully
- No — workers have no access to window, document, or the DOM
- Only read access
- Only in Chrome
Answer: No — workers have no access to window, document, or the DOM. A worker has no access to window, document, or the DOM; it is for computation only.
On the main thread, how do you receive a reply from the worker?
- worker.onreply
- worker.receive
- worker.result
- worker.onmessage
Answer: worker.onmessage. The main thread reads replies via worker.onmessage, with the payload on event.data.
How do you shut a worker down and free its thread?
- worker.stop()
- worker.terminate()
- worker.kill()
- delete worker
Answer: worker.terminate(). Call worker.terminate() from the main thread (or self.close() inside the worker) to shut it down.
After a worker is terminated, what happens to further work sent to it?
- It runs anyway
- It restarts the worker
- It is ignored because the worker is shut down
- It throws a network error
Answer: It is ignored because the worker is shut down. Once terminated, the worker no longer accepts work — new jobs are ignored.