Web Workers in JavaScript
A Web Worker is a separate JavaScript execution thread that runs in parallel with the main thread (the UI thread). It does not block the interface: heavy computation runs in the background, and the result comes back to the main thread as a message.
Theory
TL;DR
- JavaScript in the browser is single threaded: rendering, event handling and your code all live on one thread.
- A heavy synchronous loop occupies the Event Loop, and the page freezes.
- A Web Worker moves the computation onto a separate thread, so the UI stays responsive.
- The threads talk only through messages:
postMessageandonmessage, with data serialised by the structured clone algorithm. - A worker has no access to
document,window,alert()orlocalStorage, but it does havefetch,XMLHttpRequest,WebSocket,setTimeout,indexedDBandcrypto. - There are three types: Dedicated Worker, Shared Worker, Service Worker.
Quick example
// main.js
const worker = new Worker('worker.js');
worker.postMessage(1e9); // send the task
worker.onmessage = (event) => {
console.log('Result:', event.data);
};// worker.js
onmessage = (event) => {
let sum = 0;
for (let i = 0; i < event.data; i++) sum += i;
postMessage(sum); // send the result back
};Now the heavy calculation runs in the background while the UI stays responsive.
The single threaded JavaScript problem
JavaScript in the browser is single threaded, so one thread does everything at once:
- rendering the page
- handling events
- running your code
If you start a heavy loop on that thread:
for (let i = 0; i < 1e9; i++) {}
console.log('Done!');the page freezes: the interface stops responding until the loop finishes, because the Event Loop is busy.
How it works under the hood
- The main thread creates a worker with
new Worker('script.js'). - The browser starts a separate JS thread.
- The threads communicate through messages (message passing).
- The transfer is asynchronous and the data is serialised (structured clone).
- The worker has no access to the DOM,
windowordocument.
Message passing
Communication goes through the postMessage and onmessage events:
// main.js
worker.postMessage({ action: 'sum', a: 5, b: 7 });
// worker.js
onmessage = (e) => {
if (e.data.action === 'sum') {
postMessage(e.data.a + e.data.b);
}
};You can send:
- strings, numbers, objects
ArrayBuffer,Blob,File,ImageBitmap- even Transferable objects, to hand over data without copying it
Limits and the available API
A Web Worker is isolated:
- no access to
document,window,alert(),localStorage - access to
fetch,XMLHttpRequest,WebSocket,setTimeout,indexedDB,cryptoand the like
Stream processing inside a worker
A worker can use Streams and the Fetch API:
onmessage = async (e) => {
const response = await fetch(e.data.url);
const text = await response.text();
postMessage(text.length);
};That way network data is processed in the background without blocking the UI at all.
Types of Web Workers
| Type | Where it is used | Characteristics |
|---|---|---|
| Dedicated Worker | One to one | Works only with the script that created it |
| Shared Worker | Several tabs or scripts | One worker for all pages (per origin + URL) |
| Service Worker | Between the browser and the network | Caches requests, works even offline |
Shared Worker example
// shared-worker.js
onconnect = (event) => {
const port = event.ports[0];
port.onmessage = (e) => {
port.postMessage(`Received: ${e.data}`);
};
};Several tabs can connect to a single Shared Worker, for example to exchange data between tabs.
Service Worker as a special type
A Service Worker is not just a thread, it is a proxy between the browser and the network.
It intercepts HTTP requests, caches responses and makes offline work possible. Examples: PWA applications, push notifications, background sync.
// service-worker.js
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request);
})
);
});This means: if a cached response exists, serve it, otherwise load it from the network.
An example of "parallel" computation
const workers = Array.from({ length: 4 }, () => new Worker('calc.js'));
let completed = 0;
let result = 0;
workers.forEach((w, i) => {
w.postMessage(i);
w.onmessage = (e) => {
result += e.data;
if (++completed === 4) console.log('Done:', result);
};
});This spreads the computation across several threads, a kind of pseudo parallelism.
When Web Workers are worth it
| Scenario | Why it helps |
|---|---|
| Complex computation | JSON parsing, cryptography, machine learning |
| Large data processing | filtering and aggregating arrays, handling CSV |
| Image work | compression, filters, thumbnail generation |
| Games and animation | physics calculations without interface lag |
| WebSocket plus data analysis | stream processing |
A simple analogy:
Imagine the browser is a kitchen. The main thread (the main JS) is the chef who cooks the dish and serves the guests. The Web Worker is an assistant in another kitchen who chops the vegetables. The chef hands over a task, the assistant does it in the background and returns the result. Neither one gets in the other's way.
Summary:
| Component | What it does |
|---|---|
| Web Worker | A separate thread for running code |
| Main advantage | Does not block the UI |
| Data exchange | Through postMessage / onmessage |
| Limits | No access to the DOM, window, document |
| Types | Dedicated, Shared, Service Worker |
| Main uses | Background computation, caching, offline mode, sync |
Common mistakes
- Reaching for
documentorwindowinside a worker. They are not there, so the data you need has to arrive as a message. - Expecting an object to be passed by reference. Data is copied by structured clone, so large arrays are better handed over as Transferable objects (
ArrayBuffer), otherwise the copying itself becomes the bottleneck. - Sending functions, DOM nodes or class instances with methods through
postMessage. Structured clone does not support them and throws. - Spawning a new worker for every small call. Starting a thread costs time and memory, so reuse workers or keep a pool of them.
- Confusing a Web Worker with a Service Worker. The first is for computation, the second for intercepting network requests and offline mode.
- Forgetting
worker.terminate(). A worker stays alive until you stop it explicitly or the page is closed.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.