Skip to main content
Client-Server Modellesson 4 of 4 · 2 min read

State on a Stateless Protocol

HTTP forgets you between requests

HTTP is stateless: it keeps no memory between requests. Every request has to carry everything the server needs, because the server has already forgotten the last one. That looks like a defect. It is the load-bearing wall of web scaling. If no request depends on what one particular machine remembers, any machine can serve any request, and you can add or remove servers freely.

Users still expect to log in once, so state gets layered on top. The usual mechanism is a session cookie. On login the server creates a record, stores it somewhere, and hands the browser an opaque identifier, which the browser then attaches to every request after that without being asked.

Where the state actually goes

Where that record lives matters, because it is the real design choice. Keep it in one server's own memory and you have created sticky sessions. The balancer must now send each user back to the same machine every time, and when that machine dies, everyone on it is logged out. Deploys hurt for the same reason.

A shared store fixes it, one every server can reach. Each server does one fast lookup per request, roughly a millisecond, and your machines go back to being interchangeable.

Or remove the lookup altogether. A signed token packs the user identifier and an expiry into the credential itself, so a server checks the signature and trusts the contents without asking anything. The catch is taking it back. A session in a shared store dies the moment you delete it. A signed token stays valid until it expires, whatever you would prefer, so a stolen one keeps working. Short expiries and refresh tokens are the usual compromise, and teams that need instant logout drift back to server-side state.

the shape of it
BrowserServer AServer BSession storeshared, not local1. cookie2. look up3. next request4. same answer
step 1 of 4
The cookie carries a pointer; the state lives where every server can reach it.
the server remembers nothing, so the request carries the proof
Java
// Every request stands alone. This one arrives with a cookie,
// and nothing on the server remembers the last one.
Response handle(Request req) {
  String sid = req.cookie("session");     // opaque, 128 bits of random
  Session s = sessions.get(sid);          // shared store, not local memory
  if (s == null) return redirect("/login");

  // Because the state lives in the store and not in this process,
  // any of the eight app servers can answer this request.
  return render(s.userId);
}

Worked example

Sofia's startup runs its Rails app on two servers with in-memory sessions and cookie-pinned routing. During a Tuesday deploy, server 1 restarts and every user pinned to it is logged out mid-action; support gets 43 tickets in an hour, several from users whose half-filled forms vanished. The fix costs a day: sessions move to a Redis instance shared by both servers, and the load balancer drops its pinning rule. Each request now spends about 1 ms fetching the session, which is invisible next to the 80 ms the requests already took. The next deploy rolls servers one at a time and nobody notices. Later, when traffic triples, she scales from two app servers to six without touching the session code at all.

Client-Server Model: wrapping up

In the real world

  • 01Cloudflare and other CDNs terminate TLS at edge locations near users, so the expensive handshake round trips happen over a 10 ms path instead of a 150 ms one, with the edge holding warm connections back to origin.
  • 02Stripe requires idempotency keys on payment POSTs, so client retries after a timeout return the original charge result instead of billing the card twice.
  • 03Google was the main force behind HTTP/3 and QUIC, moving transport onto UDP largely to fix TCP head-of-line blocking for Search and YouTube traffic on lossy mobile networks.
  • 04Instagram serves its Django fleet stateless with session and auth state externalized, which is what lets it autoscale web workers up and down without logging users out.
  • 05WhatsApp holds long-lived connections precisely because plain request-response cannot push a message to a phone; the client connects outward once and the server delivers over that open channel.

Questions people ask

If HTTP is stateless, how does a website keep me logged in?

The server hands your browser a credential after login, either an opaque session ID stored in a cookie or a signed token like a JWT. Your browser attaches it to every request, and the server uses it to reload your identity each time. The protocol stays stateless; the state rides along in the request.

Why can't a server just send data to a client whenever it wants?

In plain HTTP only the client can open a request, and most clients sit behind NATs and firewalls that block inbound connections anyway. To push data, the client first opens a channel outward, using WebSockets, server-sent events, or long polling, and the server delivers over that established connection.

What is the practical difference between HTTP/2 and HTTP/3?

HTTP/2 multiplexes many streams over one TCP connection, but one lost packet stalls every stream because TCP delivers bytes in order. HTTP/3 runs on QUIC over UDP, so streams recover from loss independently and the connection setup is faster. The gains show up most on lossy mobile networks.

Quick review

HTTP is request-response:
client always initiates. Server cannot push without WebSocket/SSE
DNS resolution chain:
browser → recursive resolver → root NS → TLD NS → authoritative NS → IP
HTTP methods:
GET (safe, idempotent), POST (create), PUT (replace), PATCH (partial), DELETE (idempotent)
Status codes:
2xx success, 3xx redirect, 4xx client error (auth, not found), 5xx server error
Stateless protocol:
each request carries all context. Cookies/headers add state on top
TCP 3-way handshake (SYN → SYN-ACK → ACK) before HTTP data flows. TLS adds 1 to 2 more round trips
HTTP/1.1: keep-alive persistent connections. HTTP/2: multiplexing (multiple streams, 1 TCP). HTTP/3: QUIC over UDP, eliminates head-of-line blocking
the trade-off

Request-response is simple and cacheable, but the server cannot start a conversation. Anything live needs polling, SSE, or WebSocket bolted on top. Statelessness is what lets you add servers freely, and the price is that every request re-establishes its own context.

in the room

Open every design by walking the request path: DNS → CDN/LB → app server → cache → DB. Interviewers score this narration before any boxes are drawn.