The team needs real-time notifications. Should you use WebSockets, SSE, or polling? Investigate before committing architecture.
โฑ ~15 min ยท Hands-on
Users have been requesting real-time notifications โ order status updates, new messages, system alerts. The team doesn't know which technology to use. Before committing to an architecture, you need to investigate the options with a timeboxed technical spike.
A spike starts with a clear, answerable question. Vague spikes waste time.
Spike: Real-Time Notification Technology
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Question: Which real-time technology best fits
our notification requirements?
Requirements:
- Low latency (under 2 seconds)
- Moderate scale (10K concurrent users)
- Server-to-client push (bidirectional NOT required)
- Browser-native support preferred
- Must work behind corporate proxies
Non-goals:
- Chat or collaborative editing
- Real-time data sync (CRDT/OT)
Spikes are timeboxed to prevent rabbit holes. When the timebox expires, you decide with what you have โ even if the answer is "we need another spike."
This spike is limited to 4 hours. After 4 hours, the team must make a decision or explicitly schedule a follow-up spike. No open-ended research.
Explore all reasonable options before narrowing down. Don't pick the first idea โ compare systematically.
/agile-explore-brainstorm
WebSockets
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Protocol: Full-duplex TCP over HTTP upgrade
Direction: Bidirectional
Complexity: High โ connection management, heartbeats,
reconnection logic, load balancer config
Scaling: Requires sticky sessions or pub/sub adapter
Proxy: Some corporate proxies block WS upgrades
Verdict: Overkill โ we don't need bidirectional
Server-Sent Events (SSE)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Protocol: HTTP long-lived connection, text/event-stream
Direction: Server โ Client only
Complexity: Low โ built on standard HTTP, native API
Scaling: Standard HTTP load balancing works
Proxy: Works through all HTTP proxies
Browser: Native EventSource API, auto-reconnect built-in
Verdict: Perfect fit โ server push, simple, reliable
HTTP Polling
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Protocol: Standard HTTP GET on interval
Direction: Client pulls from server
Complexity: Lowest โ just regular API calls
Scaling: Wasteful โ N users ร interval = heavy load
Latency: Up to 1 full interval delay
Verdict: Too wasteful at 10K users, poor latency
Managed Service (Firebase / Pusher)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Protocol: Vendor SDK over WebSockets
Direction: Bidirectional (via vendor)
Complexity: Low to implement, high vendor lock-in
Scaling: Handled by vendor
Cost: $50-500/month at our scale
Verdict: Adds dependency and ongoing cost for simple need
Score each option against requirements:
Feasibility Complexity Fit Score
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
WebSockets 9/10 3/10 5/10 17
SSE 9/10 9/10 10/10 28 โ winner
Polling 10/10 10/10 4/10 24
Firebase/Pusher 8/10 8/10 6/10 22
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Fit = how well it matches our specific requirements
Higher is better for all columns
Brainstorming narrows the field. Now build throwaway prototypes for the top contenders to validate assumptions with real code.
SSE (highest score, best fit) and WebSockets (industry standard, worth comparing). Polling and managed services are eliminated.
/agile-explore-spike
// spike/sse-server.js โ THROWAWAY CODE
const express = require("express");
const app = express();
app.get("/notifications", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// Send a notification every 3 seconds
const interval = setInterval(() => {
const notification = {
type: "order_update",
message: "Order #1234 shipped",
timestamp: new Date().toISOString()
};
res.write(`data: ${JSON.stringify(notification)}\n\n`);
}, 3000);
req.on("close", () => clearInterval(interval));
});
app.listen(3001);
// spike/sse-client.js โ THROWAWAY CODE
const source = new EventSource("/notifications");
source.onmessage = (event) => {
const notification = JSON.parse(event.data);
showToast(notification.message);
};
// Auto-reconnect is built into EventSource โ no extra code needed
Result: Works immediately. Auto-reconnect built in.
Time to working prototype: 15 minutes.
Lines of code: 30
// spike/ws-server.js โ THROWAWAY CODE
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 3002 });
const clients = new Set();
wss.on("connection", (ws) => {
clients.add(ws);
ws.on("close", () => clients.delete(ws));
// Heartbeat to detect dead connections
ws.isAlive = true;
ws.on("pong", () => { ws.isAlive = true; });
});
// Heartbeat interval
setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
// Broadcast notification
setInterval(() => {
const notification = JSON.stringify({
type: "order_update",
message: "Order #1234 shipped",
timestamp: new Date().toISOString()
});
clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) ws.send(notification);
});
}, 3000);
// spike/ws-client.js โ THROWAWAY CODE
let ws;
function connect() {
ws = new WebSocket("ws://localhost:3002");
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
showToast(notification.message);
};
ws.onclose = () => setTimeout(connect, 1000); // manual reconnect
}
connect();
Result: Works, but adds complexity for no benefit.
- Manual heartbeat logic required
- Manual reconnection required
- Load balancer needs sticky session config
- Bidirectional capability unused
Time to working prototype: 40 minutes.
Lines of code: 80
The spike produces a clear recommendation with evidence, not just an opinion.
Spike Result: SSE Wins
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Recommendation: Use Server-Sent Events (SSE)
Evidence:
1. SSE prototype: 30 lines, 15 min, works immediately
2. WebSocket prototype: 80 lines, 40 min, added complexity
3. SSE auto-reconnect is built into the browser API
4. SSE works through all HTTP proxies (WebSockets may not)
5. SSE uses standard HTTP โ no special load balancer config
6. We don't need bidirectional communication
Trade-off accepted:
If we later need bidirectional (e.g., chat), we would
need to add WebSockets alongside SSE. This is acceptable
because chat is not on the roadmap.
Time spent: 2.5 hours of 4-hour timebox
Never merge prototype code to develop. Spike code cuts corners deliberately โ no error handling, no tests, no security. The production implementation will be written from scratch using /agile-code-tdd with proper architecture.
Capture the decision as an Architecture Decision Record (ADR) so future team members understand why SSE was chosen.
/agile-memory-learn
ADR-007: Use SSE for Real-Time Notifications
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Status: Accepted
Date: 2026-04-04
Context: Need server-to-client push for notifications
Decision: Use Server-Sent Events (SSE) over WebSockets
Reason: Simpler, fits requirements (no bidirectional needed),
native browser support, works through proxies
Alternatives considered:
- WebSockets: rejected (unnecessary complexity)
- Polling: rejected (wasteful, poor latency)
- Firebase: rejected (vendor lock-in, cost)
Consequences:
- If chat feature is added later, will need WebSockets too
- SSE limited to ~6 connections per domain in HTTP/1.1
(not a concern โ we use HTTP/2)
โ Saved to: .memory/episodic/decisions.md
The spike is done. Now turn the decision into actionable stories for the next sprint.
/agile-story-create
Stories for Sprint Backlog:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ NOTIF-001: SSE notification endpoint
As a user, I want to receive real-time notifications
so that I see order updates without refreshing.
AC: SSE endpoint at /api/notifications/stream,
sends JSON events, auto-reconnects on disconnect.
๐ NOTIF-002: Notification UI component
As a user, I want to see a notification bell with
a badge count so that I know when new events arrive.
AC: Bell icon in header, badge shows unread count,
dropdown shows last 10 notifications.
๐ NOTIF-003: Notification preferences
As a user, I want to choose which notifications I
receive so that I'm not overwhelmed by alerts.
AC: Settings page with toggles per notification type,
preferences saved to user profile.
/agile-explore-brainstorm โ systematically exploring all options with a scored comparison/agile-explore-spike โ building throwaway prototypes to validate assumptions/agile-memory-learn โ recording the decision as an ADR for future reference/agile-story-create โ converting the spike decision into actionable implementation storiesWhat does a spike produce?