Skip to main content

Service Worker

A Service Worker is a special background script the browser runs between the page and the network: it can intercept network requests, cache responses and react to events (fetch, push, sync) even when the tab is closed. In plain terms, it is a proxy server inside the browser that you control from JavaScript.

Theory

TL;DR

  • A Service Worker is a separate thread of execution, isolated from the page: no window and no document.
  • It sits between the page and the network and decides itself whether to answer from cache or go to the network.
  • Lifecycle: registration, install, activate, event-driven work, termination.
  • Core capabilities: fetch interception, Cache API, push notifications, background sync, periodic sync.
  • It works only over HTTPS (localhost is the exception) and has quotas on cache size.
  • It is the technical foundation of PWAs: offline access, instant loading, notifications without an open tab.

Quick example

sw.js:

javascript
self.addEventListener('install', (event) => { console.log('Service worker is installing'); event.waitUntil( caches.open('v1').then((cache) => cache.addAll([ '/', '/index.html', '/styles.css', '/app.js' ])) ); }); self.addEventListener('activate', () => { console.log('Service worker is active'); }); self.addEventListener('fetch', (event) => { console.log('Intercepted request:', event.request.url); event.respondWith( caches.match(event.request).then((response) => { return response || fetch(event.request); // from cache or from the network }) ); });

Registration in the page's main code:

javascript
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then(() => console.log('Service worker registered')) .catch(console.error); }

This worker stores files in the cache during installation, intercepts every request and returns the cached response, and when there is none it goes to the network.

Architecture and lifecycle

Schematically, a Service Worker sits in the middle:

text
Internet ^ | fetch request v +-----------------------+ | Service Worker | proxy between the page and the network | - intercept fetch | | - serve from cache | | - push notifications | | - background sync | +-----------------------+ ^ | +-----------------------+ | Page (browser tab)| +-----------------------+

The lifecycle has five steps:

  1. Registration. The page tells the browser where the worker file lives: navigator.serviceWorker.register('/sw.js').
  2. Installation (install). The browser downloads the script and installs it once. This is where the cache is usually filled through event.waitUntil(...).
  3. Activation (activate). Old worker versions are dropped and the new one becomes active. This is where stale caches are removed.
  4. Work (fetch / push / sync). Once active, the worker listens to events: fetch intercepts requests, push receives notifications, sync runs background tasks.
  5. Termination (idle). When the worker is not needed, the browser unloads it from memory and wakes it up again on the next event.

What a Service Worker can do

CapabilityWhat it does
Fetch interceptionIntercepts and controls every network request of the page
Caching APIStores files and responses in a cache, which enables offline work
Background syncRuns deferred tasks once the network is back
Push notificationsReceives push messages from the server and displays them
Offline-firstLets the application work without an internet connection
Periodic syncPerforms actions on a schedule
Proxy layerAdds control over requests and responses

Offline cache and a fallback page

The simplest offline scenario: try the network, and on failure return a page cached in advance.

javascript
self.addEventListener('fetch', (event) => { event.respondWith( fetch(event.request).catch(() => caches.match('/offline.html')) ); });

With no connection the user sees /offline.html instead of a browser error: the application does not break, it shows a "You are offline" message.

During activation it is worth cleaning up stale cache versions, otherwise the quota runs out quickly:

javascript
self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => Promise.all( keys.filter((key) => key !== 'v1').map((key) => caches.delete(key)) )) ); });

Push notifications and background sync

Push works even with the tab closed: the browser wakes the Service Worker up so it can handle the event.

javascript
self.addEventListener('push', (event) => { const data = event.data.json(); event.waitUntil( self.registration.showNotification(data.title, { body: data.body, icon: '/icon.png' }) ); });

Background sync lets you postpone a task until the network appears:

javascript
self.addEventListener('sync', (event) => { if (event.tag === 'sendFormData') { event.waitUntil(sendDataToServer()); } });

A typical scenario: the user is offline, the form data goes into IndexedDB, and once the network is back the Service Worker sends it to the server on its own.

Limitations and where it is used

TraitDescription
Isolated from the DOMNo window and no document, it talks to the page through postMessage
HTTPS onlyWorks on secure origins only, localhost being the exception
Limited storageThe browser grants a cache quota and may evict it
Unloaded automaticallyIt wakes on events, it does not run permanently

Where it is actually used:

  • PWAs (Progressive Web Apps): offline access, "add to home screen", instant loading.
  • News sites and chats: push notifications without an open tab.
  • Games and offline modes: preloading assets and data into the cache.
  • Web services: caching API responses and saving traffic.

A summary at a glance:

PropertyDescription
TypeBackground JS thread between the page and the network
Main functionsRequest interception, cache, push, background sync
Works with the tab closedYes
DOM accessNo
Requires HTTPSYes
Main use casePWA, offline mode, notifications

Common mistakes

  • Thinking a Service Worker is the same as a Web Worker. A Web Worker computes something in the background for one page; a Service Worker controls the network for every tab in its scope and outlives them.
  • Touching document or window inside the worker. They do not exist there; data is exchanged through postMessage and clients.
  • Forgetting to clean old caches in activate. Versions pile up and the browser eventually evicts the whole cache on quota.
  • Caching everything, including authorized API responses. Users may end up seeing someone else's or stale data.
  • Expecting a new worker to take over immediately. After an update it stays in the waiting state until the old tabs close, unless you call skipWaiting() and clients.claim().
  • Having no rollback path. A broken fetch handler can break the site for users who already installed the worker, so you need a way to unregister it.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.