TL;DR: WebSocket is a two-way connection — both browser and server talk freely (chat apps, games, collaborative editing). SSE (Server-Sent Events) is a one-way broadcast — the server pushes data to the browser (live feeds, notifications, AI token streaming). SSE is simpler and reconnects automatically. WebSocket is more powerful but more complex. Most AI-generated apps need SSE, not WebSocket.
You Asked AI for Real-Time Updates — And It Picked SSE
Here's a scenario that trips up a lot of AI-assisted builders. You're building a dashboard that shows live stock prices, or a notification feed that updates without refreshing, or maybe you're streaming AI-generated text like ChatGPT does. You tell your AI coding assistant: "Add real-time updates to this page."
You expected WebSockets. You've heard of WebSockets. They're the real-time thing, right?
But your AI generated code using something called EventSource and a route that sends text/event-stream. That's Server-Sent Events (SSE). And honestly? For most of the scenarios above, SSE is the better choice.
Let's break down what each one actually does, when you need which, and how to spot the difference in AI-generated code.
What "Real-Time" Actually Means in Web Apps
Normally, a web page works like ordering at a counter. You (the browser) ask the server for something, the server hands it back, and the conversation is over. If you want fresh data, you have to ask again. That's how every page load, every API call, and every form submission works — request, response, done.
Real-time flips this around. Instead of you constantly asking "anything new?", the server tells you the moment something changes. No page refreshes. No "pull to reload." The data just appears.
Think about it like the difference between checking your mailbox every five minutes versus having someone knock on your door the second a letter arrives.
There are two main ways to achieve this in modern web apps:
- WebSocket — Opens a two-way conversation. Both sides can talk anytime.
- SSE (Server-Sent Events) — Opens a one-way broadcast. The server talks, the browser listens.
Both solve the "real-time" problem. But they solve it very differently, and using the wrong one adds complexity you don't need.
WebSocket: A Two-Way Conversation
A WebSocket connection is like a phone call between your browser and the server. Once the call connects, either side can talk at any moment. The browser can send a chat message, the server can push a notification, the browser can send a keystroke, the server can broadcast someone else's keystroke — all on the same open connection.
This makes WebSocket the go-to choice for:
- Chat applications — Every user sends and receives messages in real time
- Multiplayer games — Player positions, actions, and game state update constantly from all directions
- Collaborative editing — Google Docs-style apps where multiple people type in the same document simultaneously
- Live auctions — Bidders send bids, server broadcasts the current highest bid to everyone
The key insight: WebSocket shines when both sides need to send data to each other in real time. If traffic only flows one direction — server to browser — WebSocket is overkill.
Here's what basic WebSocket code looks like in the browser:
// Browser connects to WebSocket server
const socket = new WebSocket('wss://myapp.com/ws')
// Connection opened
socket.onopen = () => {
console.log('Connected to server')
socket.send(JSON.stringify({ type: 'join', room: 'general' }))
}
// Receive messages from server
socket.onmessage = (event) => {
const data = JSON.parse(event.data)
console.log('Server says:', data)
}
// Connection closed
socket.onclose = () => {
console.log('Disconnected — need to reconnect manually!')
}
// Something went wrong
socket.onerror = (error) => {
console.error('WebSocket error:', error)
}
And the server side with Node.js:
// Server-side WebSocket with the 'ws' library
import { WebSocketServer, WebSocket } from 'ws'
const wss = new WebSocketServer({ port: 8080 })
wss.on('connection', (ws) => {
console.log('New client connected')
// Receive messages from this client
ws.on('message', (data) => {
const message = JSON.parse(data.toString())
// Broadcast to everyone connected
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message))
}
})
})
ws.on('close', () => {
console.log('Client disconnected')
})
})
Notice something important: there's no automatic reconnection. If the connection drops — and connections drop all the time on mobile networks, when laptops go to sleep, or when servers restart — you have to write your own reconnection logic. This is one of the most common things AI forgets to include.
SSE (Server-Sent Events): A One-Way Broadcast
Server-Sent Events work like a radio station. The server broadcasts, and the browser tunes in. Data flows in one direction only: from server to browser. If the browser needs to send something back, it uses a regular HTTP request — a completely separate channel.
This is the technology behind:
- ChatGPT's typing effect — When you see words appearing one at a time, that's SSE streaming tokens from the server
- Live notification feeds — New notifications appear without refreshing the page
- Dashboard updates — Stock prices, server metrics, or order statuses updating in real time
- Live sports scores — Scores update across all connected browsers simultaneously
- Build/deployment logs — Watching log output stream in real time (like Vercel's build logs)
Here's the big advantage: SSE is dramatically simpler than WebSocket. It runs over plain HTTP. It reconnects automatically when the connection drops. And the browser has a built-in API for it — EventSource — that handles all the messy connection management for you.
Here's what SSE looks like in the browser:
// Browser listens for server-sent events
const eventSource = new EventSource('/api/live-updates')
// Receive messages
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data)
console.log('New update:', data)
// Update your UI here
}
// Handle errors (auto-reconnects by default!)
eventSource.onerror = (error) => {
console.log('Connection lost — browser will auto-reconnect')
}
// Clean up when done
// eventSource.close()
And the server side (a basic Express route):
// Express server sending SSE
app.get('/api/live-updates', (req, res) => {
// Tell the browser this is an event stream
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
// Send an update every 3 seconds
const interval = setInterval(() => {
const data = {
price: (Math.random() * 100).toFixed(2),
timestamp: new Date().toISOString()
}
res.write(`data: ${JSON.stringify(data)}\n\n`)
}, 3000)
// Clean up when client disconnects
req.on('close', () => {
clearInterval(interval)
})
})
Compare the amount of code and complexity to the WebSocket example above. SSE is less code, less infrastructure, and the browser handles reconnection automatically. For one-way data flow, there's rarely a reason to reach for WebSocket instead.
WebSocket vs SSE: The Comparison Table
Here's the side-by-side breakdown. Save this for the next time your AI generates real-time code and you need to decide whether it picked the right tool.
| Feature | WebSocket | SSE (Server-Sent Events) |
|---|---|---|
| Data direction | Two-way (browser ↔ server) | One-way (server → browser) |
| Protocol | WebSocket protocol (ws:// or wss://) | Standard HTTP (runs over normal requests) |
| Setup complexity | Higher — requires WebSocket server, upgrade handshake | Lower — just a regular HTTP endpoint that keeps writing |
| Automatic reconnection | No — you must build it yourself or use Socket.io | Yes — built into the EventSource API |
| Data format | Text or binary (images, files, anything) | Text only (UTF-8) |
| Browser support | All modern browsers | All modern browsers (polyfill needed for very old IE) |
| Serverless compatible | No — requires persistent connection | Yes — works with streaming HTTP responses |
| Max connections | No browser-imposed limit | 6 per domain (HTTP/1.1) — no limit with HTTP/2 |
| Best for | Chat, games, collaborative editing, live auctions | AI streaming, notifications, dashboards, live feeds |
When AI Picks Which One (And Why)
If you've used ChatGPT, Claude, or any AI chat interface, you've seen SSE in action. That word-by-word typing effect? That's the AI server streaming tokens to your browser via Server-Sent Events. Your prompt goes up as a regular HTTP POST request, and the response streams back as SSE.
This is why, when you ask your AI coding assistant to build something with streaming responses, it almost always reaches for SSE. The pattern matches: you send a request, you receive a stream of data back. One direction. SSE is perfect.
Here's a quick decision framework your AI is (or should be) following:
AI picks SSE when...
- Streaming AI responses (token-by-token output)
- Live dashboard with server-pushed metrics
- Notification feed or activity log
- Real-time price or score updates
- Build/deployment log output
- Any scenario where only the server pushes data
AI picks WebSocket when...
- Chat application (users send and receive)
- Multiplayer game (everyone sends state)
- Collaborative document editing
- Live auction bidding
- IoT device control (commands + telemetry)
- Any scenario where both sides need to talk
The rule of thumb: If only the server is pushing data, use SSE. If both sides need to talk, use WebSocket. When in doubt, start with SSE — you can always upgrade to WebSocket later if you need two-way communication.
How AI Streaming Actually Works (SSE in Action)
Since so many AI-built apps involve streaming AI responses, let's look at exactly how this works. When you use the OpenAI API with stream: true, the response comes back as SSE:
// Streaming an AI response using SSE
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: 'Explain WebSockets in one paragraph'
})
})
// Read the stream
const reader = response.body.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = decoder.decode(value)
// Each chunk might contain one or more SSE messages
// Format: "data: {\"token\": \"Hello\"}\n\n"
appendToUI(chunk)
}
On the server side, your Express API streams the OpenAI response directly to the browser:
// Express endpoint that streams AI response via SSE
app.post('/api/chat', async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
const stream = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: req.body.message }],
stream: true
})
for await (const chunk of stream) {
const token = chunk.choices[0]?.delta?.content || ''
if (token) {
res.write(`data: ${JSON.stringify({ token })}\n\n`)
}
}
res.write('data: [DONE]\n\n')
res.end()
})
This is the exact pattern behind ChatGPT, Claude's web interface, and virtually every AI chat app. You send a prompt (regular HTTP POST), the server streams back tokens (SSE). Simple, effective, and no WebSocket complexity required.
What AI Gets Wrong
AI coding assistants are remarkably good at real-time code — but they have consistent blind spots. Here are the mistakes to watch for:
1. Using WebSocket When SSE Would Be Simpler
This is the most common mistake. You ask for "live updates" or "real-time data," and AI defaults to WebSocket because it's the most well-known real-time technology. But if your data only flows from server to browser — a dashboard, a notification feed, a streaming AI response — SSE does the same job with half the code and none of the connection management headaches.
What to tell your AI: "Use Server-Sent Events instead of WebSocket — I only need the server to push data to the browser."
2. Forgetting Reconnection Logic for WebSocket
The browser's WebSocket API does not reconnect automatically. When the connection drops — and it will drop — the user gets silently disconnected with no updates and no errors visible on screen. AI-generated WebSocket code frequently includes onclose with just a console.log but no reconnection attempt.
What to check for: Look for a reconnection function in the onclose handler. Something like:
// Reconnection logic AI often forgets
socket.onclose = () => {
console.log('Disconnected, reconnecting in 3 seconds...')
setTimeout(() => {
connectWebSocket() // Call the connection function again
}, 3000)
}
// Or better: exponential backoff
let reconnectDelay = 1000
socket.onclose = () => {
setTimeout(() => {
connectWebSocket()
reconnectDelay = Math.min(reconnectDelay * 2, 30000)
}, reconnectDelay)
}
If you're using SSE with EventSource, this is handled for you automatically. One more reason to prefer SSE when one-way data flow is all you need.
3. Not Handling Connection Drops Gracefully
Both WebSocket and SSE connections can drop at any time. Mobile networks switch between WiFi and cellular. Laptops go to sleep. Servers restart during deployments. AI-generated code often treats the connection as permanent and doesn't account for what happens to the UI when data stops flowing.
What to look for:
- Does the UI show a "reconnecting..." indicator when the connection drops?
- Does the app fetch missed data after reconnecting? (Events sent while disconnected are lost.)
- Is there a heartbeat/ping mechanism to detect dead connections?
4. Opening Too Many SSE Connections
Under HTTP/1.1, browsers limit you to 6 concurrent connections per domain. If each component on your page opens its own SSE connection, you can hit this limit fast and block other HTTP requests. AI doesn't always consider this when generating code for pages with multiple live-updating sections.
The fix: Use a single SSE connection that multiplexes different event types, or make sure your server supports HTTP/2 (which removes the 6-connection limit).
5. Missing CORS Headers for Cross-Origin SSE
If your SSE endpoint is on a different domain than your frontend (common with API servers), you need proper CORS headers. AI often forgets this because it generates the frontend and backend on the same origin. If your EventSource connection fails silently, missing CORS headers are usually the culprit.
The 30-Second Decision Guide
Next time you're reviewing AI-generated real-time code, ask yourself one question:
The One Question
Does the browser need to send real-time data to the server?
- Yes (chat, games, collaborative editing) → WebSocket
- No (dashboards, notifications, AI streaming, live feeds) → SSE
If you're not sure yet, start with SSE. It's simpler, reconnects automatically, works with serverless, and you can always switch to WebSocket later if your requirements change.
What to Learn Next
Now that you understand the difference between WebSocket and SSE, here's where to go deeper:
- What Are WebSockets? — A deep dive into WebSocket connections, Socket.io, authentication patterns, and scaling strategies.
- What Is a REST API? — WebSocket and SSE work alongside REST APIs. Understand the standard request-response pattern they build on top of.
- What Is Express? — The Node.js framework you'll see in most AI-generated backend code, including SSE endpoints.
- What Is Async/Await? — Real-time code is inherently asynchronous. Understanding async/await is essential for working with streams and connections.
Next Step
Building an AI chat interface? Start with SSE for streaming responses — it's what ChatGPT uses. Building a multi-user chat room? That's when you graduate to WebSocket (or Socket.io, which handles the hard parts for you). Either way, you don't need to understand the protocol internals. You need to know which one to tell your AI to use — and now you do.
FAQ
WebSocket creates a two-way connection where both the browser and server can send messages at any time — like a phone call. SSE (Server-Sent Events) creates a one-way connection where only the server sends data to the browser — like a radio broadcast. WebSocket is better for chat and collaborative apps. SSE is simpler and better for live feeds, notifications, and AI response streaming.
ChatGPT and most AI chat interfaces use SSE because the streaming pattern is one-directional: you send a prompt via a normal HTTP request, then the server streams tokens back one at a time. SSE handles this perfectly with less complexity than WebSocket. The user's messages go through regular API calls — there's no need for a persistent two-way connection.
Yes — SSE works with serverless platforms better than WebSocket because SSE runs over standard HTTP. Many serverless providers support streaming responses. WebSocket requires a persistent connection that serverless functions cannot maintain because they shut down after returning a response.
Yes. The browser's built-in EventSource API automatically reconnects when the connection drops — typically within a few seconds. This is a major advantage over WebSocket, where you must write your own reconnection logic or use a library like Socket.io that handles it for you.
Use WebSocket when both the browser and server need to send messages to each other in real time — chat applications, multiplayer games, collaborative document editing, or live auctions. If only the server needs to push data to the browser (dashboards, notifications, AI streaming, live feeds), SSE is simpler and sufficient.