Web Workers vs Service Workers: What's the Difference
Web workers run scripts off the main thread for parallel computation. Service workers intercept network requests for offline and caching. How they differ.
Web workers and service workers are both background scripts that run outside the browser’s main thread, but they solve different problems. A web worker exists to run CPU-heavy JavaScript in parallel so it doesn’t block the UI. A service worker exists to sit between your app and the network, intercepting requests to enable caching and offline behavior. The name similarity causes constant confusion, but their APIs, lifecycles, and use cases barely overlap.
Web workers: parallel computation
The browser’s JavaScript engine is single-threaded — one thread handles user input, layout, painting, and your application code. A long-running computation on that thread (parsing a large JSON payload, running an image filter, computing a hash) blocks everything else, including scrolling and clicks. Understanding why requires knowing how the event loop works: there’s no preemption, so a slow synchronous function just runs to completion while the page freezes.
A web worker spins up a genuinely separate thread with its own global scope. It has no access to the DOM, window, or your page’s variables — communication happens exclusively through postMessage() and message events, passing structured-cloned data back and forth (or transferring ownership of large buffers with Transferable objects, avoiding a copy).
// main.js
const worker = new Worker("worker.js");
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => console.log("Result:", e.data);
// worker.js
self.onmessage = (e) => {
const result = expensiveComputation(e.data.data);
self.postMessage(result);
};
Web workers are created per-tab and terminate when the tab closes or you call worker.terminate(). They’re the right tool for image or video processing, parsing large files, cryptographic operations, or any computation heavy enough to visibly stutter the UI.
Service workers: network proxy and offline
A service worker is a script the browser registers once and keeps running independently of any single page — it can outlive the tab that registered it and intercepts every fetch request the page makes, deciding whether to serve from cache, hit the network, or do both.
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((cached) => cached || fetch(event.request))
);
});
This makes service workers the foundation of offline-capable sites and PWAs: cache the app shell on install, serve it instantly on repeat visits, and fall back to cached responses when the network is unavailable. They also enable background sync and push notifications — capabilities that require the browser to wake the worker even when no tab is open.
Service workers have a distinct lifecycle (install → activate → fetch/idle) with versioning rules designed to avoid serving a broken cache: a new service worker installs alongside the old one and only takes over once existing tabs close or explicitly call skipWaiting(). That lifecycle is one of the more error-prone parts of the web platform — stale caches from a botched service worker update are a common source of “why won’t my site update” bug reports.
Side-by-side comparison
| Web worker | Service worker | |
|---|---|---|
| Purpose | Off-main-thread computation | Network interception, caching, offline |
| Lifetime | Tied to the page that created it | Persists across page loads, independent of any tab |
| Network access | Can fetch, but doesn’t intercept the page’s requests | Intercepts and controls every fetch from controlled pages |
| DOM access | None | None |
| Communication | postMessage with the page that spawned it | Controls fetch events; talks to pages via postMessage or the Clients API |
| Typical use | Parsing, image processing, crypto, heavy computation | Offline caching, PWA install, push notifications, background sync |
| Number per page | One or more, created and destroyed freely | One controller per scope, shared across all matching tabs |
When to use which — or both
They’re not mutually exclusive. A PWA might use a service worker to cache static assets and serve an offline fallback page, while also spinning up a web worker to decode a large image or run a search index off the main thread. Neither one substitutes for the other: a service worker doing heavy synchronous computation inside a fetch handler will delay every intercepted request, and a web worker has no ability to intercept network calls at all.
If your problem is “the UI freezes during this computation,” you want a web worker. If your problem is “this needs to work offline” or “repeat loads should be instant,” you want a service worker. Both run outside the main thread, which is the source of the naming overlap — but that’s essentially where the similarity ends. For performance work more broadly, see the Core Web Vitals guide for how blocking the main thread affects real user metrics.
The takeaway
Web workers parallelize CPU-bound JavaScript; service workers intercept network requests for caching and offline support. Neither touches the DOM, and neither is a drop-in replacement for the other — a heavy computation belongs in a web worker, and offline or cache-control logic belongs in a service worker. Reach for one, the other, or both depending on which problem you’re actually solving.
Keep reading
Takina · · 4 min read requestIdleCallback Explained
requestIdleCallback runs low-priority JavaScript when the browser is idle, without blocking rendering, input, or the main thread.
Takina · · 4 min read Dynamic import() in JavaScript: Code-Splitting Explained
JavaScript's dynamic import() loads a module on demand and returns a promise, letting you split bundles and defer code until it's actually needed.
Takina · · 5 min read Finding and Fixing Memory Leaks in JavaScript
A JavaScript memory leak happens when a reference outlives its usefulness and the garbage collector can't reclaim it. Common causes and how to find them.