Design real-time notification delivery to the client. Apply RADIO; compare transports and cover reconnect, ordering, and dedup.
Transport tradeoffs (poll vs long-poll vs SSE vs WebSocket), reconnect/backoff, dedup, and resume.
Requirements: deliver server-originated notifications in near-real-time, survive reconnects, avoid duplicates, keep ordering where it matters, and scale to many clients. Architecture: a single connection manager owns the transport and fans messages out to subscribers; the server pushes events; the client tracks a cursor so it can resume. Transport tradeoffs (the core of the answer): POLLING (setInterval GET) is trivial and firewall-proof but high-latency and wasteful; LONG-POLLING holds the request open until data arrives — near-real-time over plain HTTP but connection-churny; SSE (EventSource) is a one-way server→client stream with automatic reconnection and Last-Event-ID resumption BUILT IN — ideal for notifications/feeds; WEBSOCKET is full-duplex for chat/collaboration but YOU own reconnection, heartbeats, and backpressure. Pick SSE for one-way notifications; WebSocket only when the client must also push. Resilience: reconnect with EXPONENTIAL BACKOFF + JITTER (don't stampede the server after an outage); send heartbeats/pings to detect dead connections; DEDUPE with a monotonic event id and drop ids already seen; RESUME from the last-seen id on reconnect so nothing is missed; buffer while disconnected. Ordering: a per-stream sequence number lets the client detect gaps and reorder. Also handle multi-tab (a shared worker or leader election so N tabs share one connection) and coalesce bursts. Degrade gracefully: try WebSocket/SSE, fall back to long-poll where blocked.
Notification bells, live feeds, presence, chat — choosing a transport and owning reconnect/dedup/resume.
// WebSocket: YOU own reconnection, heartbeat, dedup, and resume.
function connect(url, onMsg) {
let delay = 1000, lastId = 0;
const seen = new Set();
function open() {
const ws = new WebSocket(url + '?since=' + lastId);
ws.onopen = () => { delay = 1000; }; // reset backoff
ws.onmessage = (e) => {
const m = JSON.parse(e.data);
if (seen.has(m.id)) return; // dedup
seen.add(m.id); lastId = m.id; // resume cursor
onMsg(m);
};
ws.onclose = () => {
delay = Math.min(delay * 2, 30000); // exponential
setTimeout(open, delay + Math.random() * 1000); // + jitter
};
}
open();
}
// SSE (EventSource) is simpler for one-way: reconnect + Last-Event-ID are built in.