Stateful vs Stateless: Where Does State Live?
Introduction
You just did the hard thing. In the last lesson you scaled out — you put three servers behind a load balancer, and traffic now spreads across the fleet. You add a fourth server at lunch rush, you lose one at 3 a.m. and the others carry on. Horizontal scaling, working exactly as promised.
Then a user adds a pair of shoes to their cart, clicks through to checkout — and the cart is empty.
Nothing crashed. No error in the logs. What happened is the whole reason this lesson exists: the user's "add to cart" request went to server #1, which saved the cart in its own memory. Their next request round-robined to server #2 — a server that has never heard of this user and has no cart to show. The fleet didn't break. The design broke, because the servers were stateful.
Twice in the last lesson we said the same thing without explaining it: horizontal scaling requires your app to be stateless. This is the payoff. We're going to answer the two questions that hide inside that word:
- What does "stateless" actually mean — and why is it the one property that lets a load balancer spread requests across a fleet at all?
- If a server can't remember your cart, your login, your half-finished upload... where does all that go?
The answer to the second question — you don't delete state, you move it somewhere every server can reach — is the single most important enabler of everything ahead in this course. Let's find where state lives.

First, What Is “State”?
Before stateful versus stateless means anything, pin down the word in the middle. State is anything a server remembers about a client between requests. It's the stuff that makes request #2 depend on what happened in request #1.
In a normal web app, that's a surprising amount of stuff:
- Your login / session — "this browser is signed in as Aisha."
- A shopping cart you're building up click by click.
- A multi-step form or a half-finished upload — the three chunks of a file already received.
- A live connection — an open WebSocket for a chat or a game.
- Anything cached in the server's own memory for this user, or files written to its local disk.
Here's the litmus test: if a request can only be understood using data left over from an earlier request on that same server, the server is holding state. The cart only makes sense because an earlier request put shoes in it. That leftover memory is exactly what breaks when the next request lands somewhere else.
Notice what is not on the list: the product catalog, the order history in the database, the price of an item. That's persistent data — it lives in a database and belongs to everyone, not a leftover in one server's RAM. Keep those two apart, because the whole lesson turns on the difference between state a server hoards for one client and data the system stores for everyone.
Stateful: The Server That Remembers You
A stateful server keeps your state in itself — in its own memory or on its own disk — and expects to still have it on your next request.
Picture a barista who keeps your half-made order in their head. It's fast and it's personal: they don't ask your name twice, they just remember. But that memory has a catch that only shows up when the shop gets busy — only that one barista knows. If they step away, the next barista has no idea who you are or what you ordered.
That's the fatal property of a stateful server behind a load balancer: because your cart lives in server #1's memory, every one of your future requests has to come back to server #1 — or it sees nothing. You're bound to one box. And that quietly undoes everything horizontal scaling gave you:
- The load balancer can't spread your requests freely anymore — it has to keep sending you to your one server, even if that server is slammed and others sit idle.
- If your server dies, your state dies with it. You're logged out; your cart is gone. The redundancy you built — "lose one, the others carry on" — doesn't help, because the others never had your data.
- Adding servers barely helps the users already mid-session, because their state is stuck on the old boxes.
A stateful server is a pet: named, nursed, irreplaceable — you wince when one goes down, because something lived only there. That's a fine way to run one machine. It's a terrible way to run a fleet.
Stateless: The Server That Remembers Nothing
A stateless server keeps nothing about you between requests. Every request stands on its own: it either carries everything the server needs, or it points to shared state the server can go fetch. The server handles it and forgets you the instant it responds.
Back to the coffee shop: the stateless barista doesn't hold your order in their head at all. Instead your order is written on the cup you carry — or on an order board every barista can read. Now watch what becomes possible:
- Any free barista can serve you, because your order isn't in anyone's head — it's on the cup or the board.
- One barista goes on break and you don't even notice — the next one reads the cup and keeps going.
- Three more baristas show up at the rush and they're instantly useful, because there's no "catching up" on what only the first one knew.
Translate that back to servers and you've just described the exact property a load balancer needs: if every server can handle every request, the load balancer can send any request to any server. That is the literal definition of stateless — and it's why:
Statelessness is the property that makes horizontal scaling actually work. A stateful fleet is many servers that can't cover for each other. A stateless fleet is many servers that are all interchangeable — cattle, not pets. Kill one, add ten, spread the load however you like: nobody's session is trapped on any single box.
Which raises the obvious question. If the server can't hold your cart... where did the cart go?
See It: Break the Stateful Fleet, Then the Stateless One
This is the thing to feel, not just read. Below are two services — one stateful, one stateless — each a small fleet behind one load balancer. You send the same customer's requests and watch where the load balancer routes them.
Start with the stateful side. Send a couple of requests and watch the cart land in whichever server answered first. Now send another — the load balancer round-robins it to a different server, and the cart comes back empty. Try to rescue it with sticky sessions (pin the customer to their one server) and it works again... until you hit 💥 kill that server and the cart dies with it. That's the trap: stickiness turns your one server back into a single point of failure.
Now the stateless side, where every server reads and writes the cart to a shared session store. Send requests to any server — they all find the cart, because it isn't in any server. Kill any box and the fleet just dips while the cart survives in the store. Add a box and it's serving instantly. Watch the served-versus-failed tally on each side as you go — that gap is the whole lesson.

You Don’t Delete State — You Move It Out
Here's the reframe that makes everything click. Going stateless does not mean your app forgets carts and logins. The state still exists — you just stop keeping it inside the app servers and move it to a shared place every server can reach. Externalize it.
Each kind of state gets a proper home:
- Sessions / login → an external session store (Redis, Memcached) that all servers query — or a signed token (a JWT) the client carries on every request, so the server holds nothing at all.
- Uploaded files, images, video → object storage (like S3), never the server's local disk.
- Business data — users, orders, products → the database, where it always belonged.
- Hot cached values → a distributed cache (Redis) all servers share, not each server's private memory.
Do that, and the shape of your system snaps into the exact hybrid the last lesson promised: a stateless app tier — interchangeable cattle you scale out — sitting in front of a few stateful backing services — the session store, the database, the object store — that hold all the real state and get scaled and protected on their own terms.
So "stateless system" is a bit of a lie, and it's worth saying out loud: the servers are stateless; the system absolutely has state. You didn't get rid of it — you concentrated it into a few dedicated services built to hold it. That move is the quiet hinge the whole rest of this course turns on.

Two Ways to Stay Stateless (and One Band-Aid)
Concretely, there are two clean ways to keep your app servers stateless — and one tempting shortcut that isn't really either.
1. A shared session store. The server keeps only a session-ID cookie; the actual session lives in Redis or Memcached that all servers read. Any server looks up the same store, so any server can serve you. This is the mainstream cloud-native default. The price: one extra network hop per request, and a new dependency — that store now has to be highly available, because if it's down, everyone is logged out at once.
2. A client-side token. Put the state in the client. After login the server hands back a signed token (a JWT); the browser sends it on every request; the server verifies the signature and holds nothing — no lookup at all. Beautifully stateless. The price: the token rides on every request (size), and it's genuinely hard to revoke — a token stays valid until it expires, so "log this user out now" needs a server-side blocklist, which sneaks state right back in.
And the band-aid: sticky sessions. Instead of moving the state out, you keep it in the server's memory and tell the load balancer: always send this client back to the same server (it pins them with a cookie). It works — the state is where it was left. But you've re-created every problem statelessness solved: the load can't rebalance, and if that pinned server dies the session is gone. It's a real technique you'll meet again when we cover load balancers, but it's a patch over statefulness, not a way to be stateless. The twelve-factor app manifesto is blunt about it: sticky sessions are a violation and should never be relied upon.
The Trade-off
It's tempting to file this under "stateless good, stateful bad" and move on. Resist that — the honest picture is a trade, and knowing the cost is what separates a real answer from a slogan.
| Stateful app server | Stateless app server | |
|---|---|---|
| Load balancing | must pin you to one box (sticky) | any request → any box |
| Scale out | new boxes barely help live sessions | add boxes, instantly useful |
| A box dies | that box's sessions are lost | fleet dips, state survives elsewhere |
| Per-request speed | state is a local memory read (fast) | a network hop to the store (slower) |
| Where's the risk? | many small single points of failure | one shared store you must keep alive |
Two costs deserve to be stated plainly, because beginners miss them:
Statelessness moves the single point of failure — it doesn't remove it. You made fifty app servers disposable, wonderful — but now all fifty lean on the same session store and database. Knock that store over and everyone is logged out simultaneously. You traded fifty small independent failures for one big shared dependency, and now you have to make that highly available. The hard problem of holding state didn't vanish; it got concentrated into the data tier — which, not coincidentally, is what the next whole course is about.
Statelessness costs a little latency. Reading state from your own memory is nanoseconds; reading it from Redis across the network is a round trip. You give up a sliver of per-request speed to buy scalability and resilience. For almost every web app, that's the trade of the century — but it is a trade.
Some Things Are Stateful on Purpose
One more correction before the myths, because "make it stateless" gets over-applied. Statelessness is the right default for your app and web tier — the part that runs logic and serves requests. It is not a universal law, and some things are stateful by their very nature. Fighting that is a mistake.
- Databases exist to hold state — that's the entire job.
- Message queues like Kafka keep ordered logs on specific nodes; which node matters.
- Real-time game servers hold the match and the world in memory — you can't externalize a live battle to Redis on every frame.
- WebSocket and other long-lived connections are the sharpest case: the connection itself is state, bound to one server. You can move the data behind it into a shared store, but you cannot make a live socket "land on any box."
This split is baked so deeply into how we run systems that Kubernetes ships two different primitives for it. A Deployment runs stateless pods — interchangeable, no stable identity, no attached disk; that's your cattle. A StatefulSet runs stateful pods — each with a stable name (db-0, db-1) and its own persistent disk that follows it across restarts; that's your pets, your databases and queues. The lesson isn't "everything should be stateless." It's know which tier is which, make the app tier stateless cattle, and give every piece of real state a proper stateful home you protect.
Mental-Model Corrections
"Stateless means the app stores nothing." No — the system is full of state; the app servers just don't keep it between requests. It's externalized to backing services. Stateless ≠ dataless.
"Stateless means no database." A layer confusion. Statelessness is about the app server's memory between requests — stateless servers almost always sit right in front of a very stateful database.
"Stateless is always better / more modern." It's the right default for the app tier. Databases, queues, and WebSocket/game servers are stateful on purpose. The skill is putting each kind of state in the right place.
"Sessions require sticky sessions." No — an external session store or a client token makes servers stateless with no stickiness. Sticky sessions are the thing you reach for when you haven't externalized.
"A JWT is stateless, so it's strictly better than server-side sessions." No — tokens are hard to revoke and rotate; a revocable Redis session is often the better engineering call. Each has its trade-offs.
"Going stateless removes the single point of failure." It moves it — to the shared store and database, which you must now keep highly available. Fewer, bigger dependencies, not zero.
"A stateless server can't remember me or personalize anything." It can — it re-reads your identity and state from your token or the shared store on every request. The remembering is delegated, not gone.
Try It Yourself: Find the State in Your Own App
Ten minutes turns this from a definition into an instinct you'll use in every design review. Predict first: think of an app you've built or used — where does your login actually live between clicks?
- Hunt the state. Open your browser's dev tools on any site you're logged into → Application → Cookies and Local Storage. Find the session cookie or token. That right there is the app choosing not to keep your login in one server's memory — it's on you, the client, or it's an ID pointing at a shared store. You're looking at externalized state.
- Break it in your head. Take any feature you've written that stashes something in a variable, a module-level cache, or a local file between requests. Now ask: if a load balancer sent the next request to a different copy of my server, would this still work? Every "no" is a piece of state you'd have to move to a store, a database, or the client.
- Spot the two tiers. Look at the architecture page of any product you use (many publish them). Find the split: a fleet of stateless app servers behind a load balancer, in front of a database, a cache, and an object store. Name which boxes are cattle and which are pets.
The moment you can look at any feature and instantly ask "where does this state live, and what happens if the next request lands on a different box?" — you've got the instinct this whole section is built on.
Recap & What’s Next
The most important enabler in the course, in six lines:
- State is what a server remembers about you between requests — your login, your cart, your half-finished upload, your live connection.
- A stateful server keeps that state in itself, so your requests must keep returning to the same box — which quietly breaks load balancing, redundancy, and scale-out.
- A stateless server keeps nothing between requests, so any server can serve any request — the exact property that makes horizontal scaling work.
- You don't delete state, you move it out: sessions to a session store or a token, files to object storage, data to the database. The result is a stateless app tier (cattle) in front of stateful backing services (pets).
- Statelessness moves the single point of failure into the data tier and costs a network hop — a great trade, but a trade. And some things — databases, queues, WebSockets, game servers — are stateful on purpose.
Here's what we quietly leaned on this whole lesson and never examined: a load balancer that could send any request to any server, and a shared store those servers all reached. We treated both as magic. They aren't. Soon we'll open up the load balancer — how does it actually choose a server, spread the load, and route around a dead one? — and later, that shared cache that makes the whole stateless design fast. But first, the rest of this section names the qualities we've been circling — the non-functional requirements every one of these choices is really about: how fast, how available, how consistent, at what cost.