REST and WebSockets solve different problems. REST is a request/response model: the client asks, the server answers, and the connection is done. WebSockets is a persistent, two-way channel: once it’s open, either side can send a message at any time.
They’re not really competitors — most applications use both. The question is which one fits a given feature.
1. The Core Difference
REST is built on HTTP’s request/response cycle. The client sends a request, the server returns a response, and that exchange is complete. The server can’t initiate anything; it can only answer. Each request is independent and stateless — the server doesn’t remember the last one.
WebSockets starts as an HTTP request and then “upgrades” the connection into a persistent, full-duplex channel. After the upgrade, both the client and the server can push messages whenever they want, with no new request needed each time.
| Property | REST | WebSockets |
|---|---|---|
| Model | Request/response | Persistent, full-duplex |
| Direction | Client initiates only | Either side can send |
| Connection | Short-lived per request | One long-lived connection |
| State | Stateless | Stateful (connection is held open) |
| Server push | No (client must poll) | Yes (native) |
| Caching | Built into HTTP | Not cacheable |
| Best for | CRUD, fetching data | Real-time, bidirectional updates |
2. The Problem WebSockets Solves: Server Push
With REST, the server can’t tell the client “something changed.” If a client needs live updates — new chat messages, a stock price, another player’s move — it has to ask repeatedly. There are three ways to do that, each with drawbacks:
- Short polling: the client sends a request every few seconds. Most responses are “nothing new,” wasting requests and adding latency (an update can be up to one interval late).
- Long polling: the server holds the request open until it has something to send, then responds; the client immediately reconnects. Better latency, but still a new request per message and held connections on the server.
- Server-Sent Events (SSE): a one-way stream from server to client over HTTP. Good for push-only feeds, but the client can’t send back over the same channel.
WebSockets removes the polling entirely. One connection stays open, and the server pushes the moment something happens — no wasted requests, near-instant delivery, and the client can talk back over the same channel.
3. The WebSocket Handshake
A WebSocket connection begins as an ordinary HTTP request with an Upgrade header:
Client request:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Server response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
After the 101 Switching Protocols response, the TCP connection is no longer speaking HTTP — it’s a WebSocket carrying lightweight message frames in both directions. Because the connection runs over standard ports (80/443) and starts as HTTP, it works through most proxies and firewalls.
4. A Quick Code Comparison
REST (fetch a resource):
// Client asks, server answers, done.
const res = await fetch('https://api.example.com/messages');
const messages = await res.json();
WebSockets (subscribe to a live stream):
// Open once, then receive messages as they arrive.
const socket = new WebSocket('wss://api.example.com/chat');
socket.onopen = () => socket.send(JSON.stringify({ type: 'join', room: 'general' }));
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
renderMessage(message); // The server pushed this — we didn't ask for it.
};
socket.onclose = () => reconnectWithBackoff();
Note the onclose handler: a persistent connection will drop sometimes (network changes, server restarts), so production WebSocket clients need reconnection logic with backoff. REST has no equivalent concern — each request stands alone.
5. Scaling and Operational Trade-offs
REST scales the way the web scales. Requests are stateless, so any server can handle any request, and you can put a CDN or cache in front of it. Load balancing is trivial.
WebSockets is harder to scale because state lives in the connection:
- Connection limits: each open connection consumes a file descriptor and some memory. A single server can hold tens of thousands of idle connections, but it’s a real resource you have to plan for.
- Sticky state across instances: if user A is connected to server 1 and user B to server 2, server 1 can’t directly push a message to user B. You need a pub/sub layer (commonly Redis, NATS, or a managed service) so any instance can broadcast to clients connected elsewhere.
- Reconnection and auth: clients reconnect after drops, and you have to authenticate on connect (and often re-authenticate), since there’s no per-request header to check like in REST.
- Load balancers: they must be configured to allow the
Upgradehandshake and to hold long-lived connections rather than timing them out.
None of this is a reason to avoid WebSockets — it’s the cost of real-time delivery, and it’s well worth it when you need it.
6. Which to Use
Use REST for:
- Standard CRUD: loading a page, fetching a list, submitting a form.
- Anything you want cached or served from a CDN.
- Public APIs where broad client compatibility and simple debugging matter.
Use WebSockets for:
- Chat and messaging.
- Live notifications and presence (“user is typing”, “online now”).
- Live dashboards, collaborative editing, multiplayer games, and trading screens — anything where the server needs to push updates the instant they happen.
In practice, most real-time apps use both: REST to load the initial state and history, and WebSockets to stream changes from then on. A chat app fetches the last 50 messages over REST when you open a room, then switches to a WebSocket for everything that arrives afterward.
7. What About SSE and HTTP/2?
If you only need server-to-client push (a live feed, notifications) and never need the client to send over the same channel, Server-Sent Events are simpler than WebSockets — they’re plain HTTP, reconnect automatically, and need no special protocol upgrade.
And on HTTP/2 or HTTP/3, the cost of multiple REST requests drops because many requests share one connection (multiplexing), which makes polling less expensive than it was on HTTP/1.1. That doesn’t replace WebSockets for true bidirectional, low-latency messaging — but it does mean “just use REST” is viable for more cases than it used to be.
Pick the simplest tool that delivers the experience you need: REST by default, SSE for push-only feeds, and WebSockets when both sides need to talk in real time.