What Is a SharedWorker? Explained
A SharedWorker runs one script shared across every tab and window of the same origin — a single background thread they can all connect to and message.
A SharedWorker is a background JavaScript thread that multiple browsing contexts — different tabs, windows, or iframes of the same origin — can all connect to and communicate with at once. Unlike a dedicated Web Worker, which belongs to exactly one page and dies when that page closes, a SharedWorker persists as long as at least one connected context stays open, giving every open tab of your app access to the same running instance and the same in-memory state.
How it differs from a dedicated Web Worker
A dedicated worker is created with new Worker(...) from a single page, and it talks to that page over a simple postMessage/onmessage pair — one worker, one owner. If you open the same app in two tabs, you get two entirely separate dedicated workers, each with its own memory and no way to see what the other is doing.
A SharedWorker is created with new SharedWorker(...), and the browser guarantees that every context requesting the same worker script (from the same origin) connects to a single running instance rather than spawning a new one each time. Open the same app in five tabs, and all five share one worker with one set of in-memory state — a cache, a WebSocket connection, an in-progress computation — that none of them have to duplicate.
Connecting from multiple tabs (MessagePort)
Because a SharedWorker can have many connected clients, its messaging model is a level more explicit than a dedicated worker’s. Each connecting context gets its own MessagePort from the worker’s onconnect event, and messages travel over that specific port rather than a single shared inbound channel:
// inside the shared worker script
const ports = [];
onconnect = (event) => {
const port = event.ports[0];
ports.push(port);
port.onmessage = (e) => {
// broadcast to every connected tab
for (const p of ports) p.postMessage(e.data);
};
};
// inside a page
const worker = new SharedWorker("worker.js");
worker.port.start();
worker.port.postMessage("hello from this tab");
worker.port.onmessage = (e) => console.log("received:", e.data);
The worker script decides what to do with each connection — track it in a list to broadcast to everyone, as above, or treat each port independently.
SharedWorker vs Web Worker vs Service Worker
| Web Worker | SharedWorker | Service Worker | |
|---|---|---|---|
| Scope | One page | Same-origin tabs/windows | Same-origin, network-proxy role |
| Lifetime | Dies with its page | Lives while any client is open | Persists independently, can wake for events |
| Shared state across tabs | No | Yes | Indirect (via caches, messaging) |
| Typical use | Offloading CPU-heavy work | Cross-tab coordination, shared connections | Offline caching, push notifications |
| Messaging model | Direct postMessage | MessagePort per connection | postMessage + fetch interception |
Practical use cases
The scenarios where a SharedWorker earns its complexity all involve cross-tab coordination: maintaining a single WebSocket connection to a server so five open tabs don’t each open their own redundant connection and compete for the same real-time updates; centralizing an expensive in-memory cache or computed dataset so every tab reads from one copy instead of recomputing it independently; or coordinating locks and leader-election logic so only one tab performs a background sync task at a time, with the others staying passive.
A simpler alternative worth knowing about for pure cross-tab messaging, without needing a persistent background thread, is BroadcastChannel — it lets same-origin contexts send messages to each other directly, but it doesn’t give you a shared thread to hold state or do work in the way a SharedWorker does. For actual parallel computation shared across contexts, SharedArrayBuffer is the lower-level primitive for sharing raw memory rather than passing messages.
The takeaway
A SharedWorker is the one worker type designed explicitly for multiple tabs of the same app to cooperate: one running instance, reached through per-connection MessagePorts, that persists as long as any tab needs it. Reach for it when several open tabs genuinely need to share a connection, a cache, or coordination logic — and reach for a plain Web Worker or service worker instead when your problem doesn’t actually need state shared across tabs, since a SharedWorker’s connection-management model is real overhead you shouldn’t pay for a single-tab problem.
Keep reading
Takina · · 5 min read ResizeObserver vs IntersectionObserver: Which to Use
ResizeObserver watches an element's size; IntersectionObserver watches its visibility. Both replace scroll/resize listeners — here's when to use each.
Takina · · 4 min read setTimeout vs setInterval in JavaScript
setTimeout runs a callback once after a delay; setInterval repeats it on a fixed cadence — but drifts under load in ways setTimeout recursion avoids.
Takina · · 4 min read How Garbage Collection Works in JavaScript
JavaScript frees memory automatically by tracking reachability, not reference counts. How the mark-and-sweep algorithm works and what causes leaks anyway.