cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

WebSocket & Server-Sent Events

Learn long polling, WebSocket, Server-Sent Events, and how to choose a real-time transport without opening external network connections.

By the end, you can
  • 01
    Explain the transport choicesCompare short polling, long polling, SSE, and WebSocket with everyday analogies.
  • 02
    Use the browser APIs safelyName WebSocket ready states, close codes, EventSource reconnection, and SSE text fields.
  • 03
    Choose the right toolPick a transport for chat, scores, notifications, games, and slow job status checks.

Real-time means “tell me when it happens”

Most network code you have seen so far is request/response: your code asks a server for something, the server replies, and the connection is done. That is perfect for loading a profile, saving a form, or fetching JSON. Real-time user interfaces have a different feeling: scores change while you watch, a chat bubble appears without refreshing, and a dashboard moves as data arrives.

JavaScript gives you several ways to create that feeling. They are not interchangeable magic words. They differ in direction, reconnection behavior, binary support, how well they fit proxies, and how much work your app must do. This lesson covers the four names you will hear most often: short polling, long polling, WebSocket, and Server-Sent Events.

Real-life analogyA live score page that updates itself

The question is not “which one is newest?” It is “who needs to talk, how often, and what kind of data?” A score ticker mostly listens. A multiplayer game talks both ways. A report status page might only need to check once a minute.

In real life: Refresh the score every minute
In JavaScript: A timer calls fetch() again and again
In real life: Ask once and wait for the next score
In JavaScript: One request waits until an update or timeout, then you ask again
In real life: The score page sends every update
In JavaScript: The server streams text events to the browser
In real life: You can send a message about the match
In JavaScript: Client and server send messages any time over one open connection

Where the analogy stops: Networks fail, servers restart, tabs sleep, and proxies buffer data. The analogy explains direction and timing, not reliability guarantees.

About the demos

This site is static and never connects to external hosts. The WebSocket and long-polling demos use an in-page fake server that follows the real API shapes. The SSE demo first tries a browser-only data: event stream; if the browser blocks it, it falls back to a tested parser.

Long polling: request, wait, answer, repeat

INTERACTIVE

Short polling is the simplest pattern: set a timer and ask the server, “anything new?” every few seconds. It works almost everywhere because it is just normal HTTP. The cost is wasted work: quiet periods still create empty requests, and updates can wait until the next timer tick.

Long polling changes one detail. The browser sends a request, but the server does not answer immediately. It holds the request until there is a new event or a timeout. As soon as the response arrives, the browser processes it and immediately starts the next request. You still pay one request per message, but you avoid a stream of empty “no change” replies.

Short polling vs long polling timeline
The request patternHTTP-ish timeline
// Short polling: ask every 10 secondsGET /updates?since=0  -> no changeGET /updates?since=0  -> update at 20s // Long polling: ask once and waitGET /updates?since=0  -> server holds                         update at 18s
Timeline10s interval
0srequest returns no changeone request waits until 18s, then returns update
10srequest returns no changeno extra request yet
20srequest returns update after 2s delayno extra request yet
30srequest returns no changeno extra request yet
40srequest returns no changeno extra request yet
Try it yourself

Short polling makes 6 requests per minute and can wait 2s after the update. Long polling keeps one request waiting, so the update is returned immediately in this model.

Move the update time. Short polling waits for the next scheduled request; long polling lets the server answer the already-open request.

Long polling is useful when WebSocket or streaming connections are blocked by old infrastructure, or when the server stack is already built around ordinary request handlers. It is also a good fallback mental model: a client loop, a timeout, and careful error handling. You saw similar retry thinking in the Async patterns lesson.

WebSocket: one open, two-way conversation

STEP THROUGH

A WebSocket starts with new WebSocket("wss://…", protocols?). After the opening handshake, the browser and server can both send messages at any time. That makes it the usual choice for chat, multiplayer games, collaborative cursors, and other interfaces where the client also speaks often.

Real-life analogyreadyState is the phone’s status

Calling someone does not mean you can talk instantly. First the phone dials, then the call connects. WebSocket has the same safety rule: send only after the open event, when readyState is OPEN.

In real life: Dialing
In JavaScript: CONNECTING / readyState 0
In real life: Connected call
In JavaScript: OPEN / readyState 1; send() is allowed
In real life: Hanging up
In JavaScript: CLOSING / readyState 2
In real life: Hung up
In JavaScript: CLOSED / readyState 3
In real life: Saying ‘still there?’ on a quiet call
In JavaScript: Heartbeat ping/pong messages

Where the analogy stops: The browser WebSocket API does not expose protocol-level ping frames to page JavaScript. Apps often send their own small heartbeat messages instead.

WebSocket lifecycle replay
Step 0 of 8Ready
Your turn: follow the blue line

This is an instrumented replay using a simulated WebSocket. It follows the real WebSocket event names and readyState values without opening any network connection.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
 socket.addEventListener("open", () => {  socket.send("hello bots");}); socket.addEventListener("message", (event) => {  console.log(event.data);}); socket.addEventListener("close", (event) => {  console.log(event.code, event.reason);});
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

A few details matter in production. send() accepts strings, Blob, ArrayBuffer, and typed-array data only when the socket is open; sending while connecting throws InvalidStateError. bufferedAmount shows bytes queued but not yet sent. Close code 1000 means normal, 1001 means going away, and 1006 is special: apps cannot send it, but browsers report it when the connection disappears abnormally. WebSocket does not reconnect automatically. If the page was loaded over HTTPS, use wss://.

FakeWebSocket chat room
WebSocket client shapePop out in the code editor (opens in a new tab)JavaScript
const socket = new WebSocket("wss://example.test/chat", "chat.v1"); socket.addEventListener("open", () => {  socket.send("Hello room!");}); socket.addEventListener("message", (event) => {  appendToLog(event.data);}); socket.addEventListener("close", (event) => {  showCloseCode(event.code, event.reason);});
Simulated roomCLOSED
  1. Disconnected. Press Connect to start the fake room.
Try it yourself

This is a simulation: no network leaves the page. It uses the real WebSocket event names and readyState numbers. Current state: CLOSED.

Connect, send a message, trigger a server broadcast, drop the connection with reported code 1006, then retry with a tiny backoff strategy.

The playground’s drop button reports 1006 to make the abnormal case visible. The reconnect button uses a tiny backoff: wait a bit, create a new socket, then wait longer on the next failure. Real apps add maximum delays, online/offline checks, and authentication refreshes.

Newer API note

WebSocketStream is a newer streams-based idea available only in Chromium-based browsers at the time of writing. The widely used API is still WebSocket.

Server-Sent Events: a one-way text stream

INTERACTIVE

Server-Sent Events use EventSource to receive a text/event-stream response over HTTP. The direction is one-way: server to browser. That is a strength when your app mostly listens, because the browser gives you automatic reconnection, named events, event ids, and retry timing without writing a custom socket manager.

The stream is plain text. Lines beginning with event: name the event for addEventListener(name). Lines beginning with data: become event.data. id: sets lastEventId; on reconnect, browsers send it back as the Last-Event-ID header when using normal HTTP. retry: suggests how long to wait before reconnecting. Unnamed events go to onmessage; named events do not.

Server-Sent Events without a server
EventSource client shapePop out in the code editor (opens in a new tab)JavaScript
const source = new EventSource("data:text/event-stream,..."); source.addEventListener("score", (event) => {  console.log(event.lastEventId, event.data);}); source.onmessage = (event) => {  console.log("message", event.data);}; source.close(); // stop reconnecting
SSE text formattext/event-stream
retry: 1200id: kickoffevent: scoredata: Owls 0, Foxes 0 id: goal-1event: scoredata: Owls 1, Foxes 0 id: note-1data: Halftime snacks are ready
Events receivedparser
  1. Press a button to receive or parse events.
Try it yourself

Not started Mode: parser. The fallback parser is a pure function tested in Node.

The real browser attempt never contacts an external host: the stream text is embedded in a data: URL. If the browser blocks that, the same event-stream text is parsed locally.

SSE is text-only, but text can still contain JSON. It is great for notification feeds, live sports scores, progress messages, logs, and dashboards. Use new EventSource(url, { withCredentials: true }) when cookies or HTTP authentication should be included. In many browsers, several SSE tabs on the same HTTP/1.1 domain can run into the per-domain connection limit; HTTP/2 usually improves that because streams share one connection.

Choosing a transport

SORT IT
Polling, Server-Sent Events, and WebSocket compared
FeaturePolling / long pollingServer-Sent EventsWebSocket
DirectionClient asks; server answersServer → clientBoth directions any time
Protocol shapeNormal HTTP requestsHTTP response with text/event-streamWebSocket connection after an HTTP upgrade
ReconnectionYour loop decidesBuilt in with retry and Last-Event-IDYour app decides
Binary supportYes, as normal responsesNo, UTF-8 textYes, binary frames and text
HTTP/proxy friendlinessExcellentGood, though buffering and connection limits matterSometimes needs proxy support for upgrades
Typical usesSlow job status, legacy fallbackScores, notifications, progress, dashboardsChat, games, collaboration, live control
Choose the transport
  • Live sports scores: server pushes updates, browser mostly listens
  • Multiplayer game: every player sends moves and receives state quickly
  • Check whether a report finished once every minute
  • Chat room: people type any time and receive messages any time
  • Notifications feed: new items appear while the tab is open
  • Very old infrastructure where streaming is blocked by proxies
Try it yourself
0 of 6 correct

Sort each scenario by the simplest transport that fits its direction and timing needs.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

A practical rule of thumb: choose the boring thing that satisfies the product. If rare updates are fine, poll. If the server streams text and the browser only listens, start with SSE. If both sides need to talk at any moment, use WebSocket. If infrastructure says no, long polling is still a respectable fallback.

Where you will use this

In a real app, the code around the transport matters as much as the transport: parse messages, update state, show connection health, clean up when a component unmounts, and avoid duplicate listeners. These examples omit framework state on purpose so the transport shape is visible.

Chat-style WebSocket clientJavaScript
const socket = new WebSocket("wss://example.com/chat", "chat.v1"); socket.addEventListener("open", () => {  socket.send(JSON.stringify({ type: "join", room: "lobby" }));}); socket.addEventListener("message", (event) => {  const message = JSON.parse(event.data);  renderChatMessage(message);}); socket.addEventListener("close", (event) => {  scheduleReconnect(event.code);});
Notification-style EventSource clientJavaScript
const feed = new EventSource("/notifications/stream", {  withCredentials: true,}); feed.addEventListener("notification", (event) => {  const item = JSON.parse(event.data);  addNotification(item);}); feed.onerror = () => {  showStatus("Reconnecting...");};
Transport chooser as codePop out in the code editor (opens in a new tab)JavaScript
function chooseRealtimeTransport({ clientTalks, binary, textUpdates, rareUpdates }) {  if (clientTalks || binary) return "WebSocket";  if (textUpdates) return "Server-Sent Events";  if (rareUpdates) return "Polling";  return "Long polling";}

The cleanup step is easy to forget. Components should call socket.close() or eventSource.close() when they no longer need updates. Otherwise, a hidden tab or unmounted view can keep receiving data and doing work.

Common misconceptions

  • “Real-time always means WebSocket.” If data only flows from server to browser, SSE is often simpler and reconnects for you.
  • “SSE can replace every WebSocket.” SSE is one-way and text only. Use another request for client actions, or choose WebSocket.
  • “WebSocket reconnects automatically.” It does not. A close event is the end until your code creates a new socket.
  • “Close code 1006 is something I should send.” It is reserved for reporting abnormal closure. Application code should not send it.
  • “Long polling is fake real-time.” It is a real and useful pattern, especially as a compatibility fallback, but it pays request overhead per message.
  • “A heartbeat fixes every network problem.” Heartbeats detect quiet broken connections; they do not guarantee delivery or replace retries.

Practice exercises

4 EXERCISES
Exercise 1 · Warm-upPredict the connecting send

Read the code and predict the one line printed to the console.

Starter codePop out in the code editor (opens in a new tab)JavaScript
class TinySocket {
  constructor() { this.readyState = 0; }
  send() {
    if (this.readyState === 0) throw new Error("InvalidStateError");
    console.log("sent");
  }
}
const socket = new TinySocket();
try {
  socket.send("hi");
} catch (error) {
  console.log(error.message);
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeName the open state

    Type the readyState number that means a WebSocket is open.

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeRead an SSE text stream

      Predict how many data fields the small parser counts.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const lines = ["event: score", "data: Owls 1", "", "data: plain", ""];
      console.log(lines.filter((line) => line.startsWith("data:")).length);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeChoose for a real-time feature

        Use the clues in the booleans to choose the transport the program prints.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const needsClientMessages = true;
        const binaryFrames = true;
        console.log(needsClientMessages && binaryFrames ? "WebSocket" : "SSE");

        Answer, then press Check. Spacing and letter case don’t matter.

          Check your understanding

          7 QUESTIONS
          Real-time JavaScript quiz · 7 questionsScore: first tries count
          1. Question 1 of 7Which comparison best describes long polling?

            Choose an answer to see the explanation.

          2. Question 2 of 7What does this readyState lookup print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const states = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
            console.log(states[1]);

            Choose an answer to see the explanation.

          3. Question 3 of 7Which WebSocket statement is accurate?

            Choose an answer to see the explanation.

          4. Question 4 of 7Does this SSE text contain a named event?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const stream = "event: score\ndata: 2-1\n\ndata: plain\n\n";
            console.log(stream.includes("event: score"));

            Choose an answer to see the explanation.

          5. Question 5 of 7Which EventSource feature is built in?

            Choose an answer to see the explanation.

          6. Question 6 of 7Which transport would you choose for multiplayer movement updates?

            Choose an answer to see the explanation.

          7. Question 7 of 7What does this transport check print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const supportsBinary = { sse: false, websocket: true };
            console.log(supportsBinary.websocket ? "binary ok" : "text only");

            Choose an answer to see the explanation.

          Key takeaways

          • Polling asks on a schedule; long polling waits to answer one request.
          • WebSocket is a two-way open connection. Send only in OPEN, and reconnect yourself.
          • Server-Sent Events are one-way text streams with named events, ids, retry, and automatic reconnection.
          • Choose by direction, update frequency, binary needs, infrastructure, and failure behavior.

          Real-time browser code is choosing the smallest reliable channel that delivers updates when users need them.

          Up next: XMLHttpRequest — the older request API you’ll still meet in legacy code.

          CompleteFrontend Clear concepts. Working examples.