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
windowand nodocument. - 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:
fetchinterception, 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:
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:
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:
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:
- Registration. The page tells the browser where the worker file lives:
navigator.serviceWorker.register('/sw.js'). - Installation (
install). The browser downloads the script and installs it once. This is where the cache is usually filled throughevent.waitUntil(...). - Activation (
activate). Old worker versions are dropped and the new one becomes active. This is where stale caches are removed. - Work (
fetch/push/sync). Once active, the worker listens to events:fetchintercepts requests,pushreceives notifications,syncruns background tasks. - 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
| Capability | What it does |
|---|---|
| Fetch interception | Intercepts and controls every network request of the page |
| Caching API | Stores files and responses in a cache, which enables offline work |
| Background sync | Runs deferred tasks once the network is back |
| Push notifications | Receives push messages from the server and displays them |
| Offline-first | Lets the application work without an internet connection |
| Periodic sync | Performs actions on a schedule |
| Proxy layer | Adds 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.
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:
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.
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:
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
| Trait | Description |
|---|---|
| Isolated from the DOM | No window and no document, it talks to the page through postMessage |
| HTTPS only | Works on secure origins only, localhost being the exception |
| Limited storage | The browser grants a cache quota and may evict it |
| Unloaded automatically | It 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:
| Property | Description |
|---|---|
| Type | Background JS thread between the page and the network |
| Main functions | Request interception, cache, push, background sync |
| Works with the tab closed | Yes |
| DOM access | No |
| Requires HTTPS | Yes |
| Main use case | PWA, 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
documentorwindowinside the worker. They do not exist there; data is exchanged throughpostMessageandclients. - 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()andclients.claim(). - Having no rollback path. A broken
fetchhandler can break the site for users who already installed the worker, so you need a way to unregister it.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.