Every useful web app has to remember things between requests. When you log in, the app remembers who you are on the next page. When you add an item to a cart, it's still there when you check out. When you upload a large file in chunks, the server holds onto the pieces until they're all in. That remembered data — sessions, carts, partial uploads, anything carried from one request to the next — is what engineers call state.
The interesting question isn't whether an app has state. It always does. The question is where that state lives — and that single choice quietly decides how easily your system can grow.
The problem: stateful servers
The tempting approach is to keep state right where it's convenient: in the server's own memory. The first time you log in, server A creates a session object and holds it in RAM. That's fast and simple — until you run more than one server.
Now there's a catch. Your next request might land on server B, which has never heard of you and has no session in memory. Suddenly you appear logged out. To paper over this, systems often use sticky sessions: once you've been handled by server A, the load balancer pins you to server A for the rest of your visit.
- Server 2Holds this client's session in local memory — so the client is stuck with it.
Sticky sessions "work," but they make the whole fleet rigid. Because each user's state lives on one particular machine, the servers are no longer interchangeable. You can't freely add a new server (it starts empty and gets no existing users), you can't cleanly replace one for maintenance, and you can't balance load evenly because traffic is glued to wherever each user first landed.
Worst of all is failure. If server A crashes, it doesn't just go offline — it takes every session, cart, and half-finished upload it was holding down with it. Those users are abruptly logged out and lose their in-progress work.
Local state is a trap that hides until you scale. On one server everything looks fine. The pain only shows up when you try to add a second server, replace a sick one, or survive a crash — exactly the moments you most need things to go smoothly.
The solution: statelessness
A stateless server keeps no per-request state in its own local memory. Each request arrives carrying (or pointing to) everything needed to handle it, the server does its work, and it forgets the request entirely once the response is sent. Nothing important is left behind on that particular machine.
The state didn't vanish — it moved to a shared store that every server can reach. Sessions and carts go into a shared database or cache like Redis; uploaded chunks go into shared object storage; sometimes the user's identity is even carried inside a signed token on each request, so the server doesn't have to look it up at all.
- ServerHolds no per-user state — any server can serve any request.
- Shared storeWhere session/state lives so every server can reach it.
Once state lives in a shared place, the servers become truly interchangeable. Any server can handle any request, because whatever it needs — your session, your cart, your upload — it fetches from the shared store rather than from its own memory. There's no reason to pin you to a particular machine anymore.
That's a profound shift. If a server crashes mid-request, you simply retry and a different server picks it up, your session intact in the shared store. Adding capacity is as easy as booting another identical, empty server.
Stateless does not mean "no state." It means no state on the individual server. The state is still there — it just lives in one shared, durable place instead of being scattered across machines that might disappear. "Stateless server, shared state" is the pattern to remember.
Why it matters
Statelessness is the quiet precondition for two of the most important ideas in system design. It's what makes horizontal scaling possible: when servers hold no local state, you can add or remove identical machines on demand without anyone losing their session. And it's what makes load balancing clean — the balancer is free to send each request to whichever server is least busy, instead of being forced to honor sticky sessions.
The takeaway is simple: keep your servers forgetful and put the memory somewhere shared. Stateless servers are cheap to add, safe to lose, and easy to balance — which is exactly what you want when your app starts to grow.