Stateless

Concept

A design where the server remembers nothing between requests and receives what it needs with each one, which is what makes adding and removing servers easy.

Why it exists

With one server, remembering the user was the easiest thing to do. Someone logs in, the server writes down in its own memory who this connection belongs to, and the next request looks that record up. Requests carry a short key and nothing else, because the server already knows the rest, which also keeps the traffic between browser and server small.

The moment traffic grows and a second server appears, the arrangement breaks. Server one handled the login and holds the record; the next request lands on server two, which has no idea who this is and answers by asking them to log in. What the user sees is logging in successfully, refreshing, and being logged out, then logging in again and having it work. Only half the requests fail, so it barely reproduces, and whoever is debugging it spends the afternoon reading login code that has nothing wrong with it.

There was a stopgap: configure the front layer to pin each user to the same server every time. That means the moment a server dies, exactly those users all get logged out, and adding a server doesn't spread load evenly because users already attached never move. Stateless solves it from the other direction — make the server stop remembering, so any server can answer any request identically, and pinning becomes a setting nobody needs.

How it works

All of it is moving the memory the server was holding somewhere else. There are two destinations, and afterward the server handles a request and leaves nothing behind.

  1. Put it in the request. A JSON Web Token (JWT), the format for passing authentication details in a signed token, is the standard approach. Who this is and how long it's valid get written on a note, signed, and handed to the user, who sends it along with every request. The server only verifies the signature and asks nobody anything, which means it can answer without a single round trip to storage.
  2. Put it in shared storage. Write the record into a database every server can read, or into a fast temporary store, and have servers look it up per request. The memory still exists, but it lives outside the servers, so every server sees the same value.
  3. Move the files too. Save an uploaded file to that server's local disk and the file isn't there when the next request lands elsewhere. Upload to storage every server can reach and record only the address. This is usually the last piece of a migration to finish, because uploads are the part that works fine in development.
  4. Leave no residue after handling. One design that stashes an intermediate value in a server variable to use on the next request is enough to make the whole system not stateless, and these are the hardest cases to find because they usually work in testing.

Checking whether you got there is simple. Kill one server without warning and see whether the users it was serving at that moment get errors. If nobody notices, it's stateless. Teams that run this on purpose, on a schedule, learn about the exceptions before an incident does.

Where it shows up

It's assumed wherever the number of servers has to change freely.

The first is horizontal scaling, handling load by adding machines. The approach only works if a freshly started server can take requests immediately with no memory of anything. Autoscaling, which adds and removes machines automatically with load, goes one step further: when it scales down it simply destroys a server, so anything only that server knew disappears with it.

The second is distribution out front. A load balancer, which spreads requests across servers, can distribute evenly with no constraints, and the setting that pins a user to one server stops being necessary. Traffic then lands wherever there is capacity rather than wherever a user happened to start.

The third is deployment. Pulling a few servers out at a time and replacing them with a new version becomes possible, and the premise that a server is safe to pull comes from statelessness. Without it, every deployment needs a maintenance window.

The fourth is serverless, running functions without managing servers directly. A fresh execution environment can appear on any invocation, so stateless isn't a preference there — it's the only option.

How it sounds at work

"This feature holds things in server memory, so it isn't stateless. It breaks if we add machines."

Pointing out that the current structure can't scale out.

"Move the session into shared storage and it becomes stateless."

A proposal to solve it by relocating the memory outside the server.

"It's stateless, so we can restart servers whenever we want."

Meaning deployment and scale-down carry no special constraints.

At Pagedeck the problem arrived the day a second server went live. Sixty support tickets came in before noon, all about being logged out. Theo patched it by pinning users to servers in the front layer, and that same week moved login records into shared storage.

After the move the pinning came off. The following month a server stopped abruptly, and only the requests it was holding failed — users who clicked again were served normally by another machine. With pinning still in place, every user attached to that server would have been logged out at once, and the incident would have shown up as a support queue instead of a graph.

What people get wrong

The most common misreading takes stateless to mean there is no state. The name invites it. But who the user is and what's in their cart still have to live somewhere. Stateless says only that the server isn't the one holding it; the state moved into the request or into shared storage. Understand it as removal and you can end up building a system that stops entirely when shared storage stops, with no explanation for why.

Believing stateless is always the better choice is the other one. Scaling and deploying get easier, so the belief sets quickly. There are two costs. Every request either carries authentication with it or queries shared storage, so each request costs slightly more. And with the in-the-request approach, revoking a note already issued is hard: the server remembers nothing, so an issued token keeps working until it expires, and logout or a permissions change doesn't take effect immediately. Practice is to keep expiry short with frequent reissue, or to keep only a revocation list in shared storage — a small amount of remembering, reintroduced deliberately, in exchange for being able to cut access on demand.

Easily confused with

TermWhere the memory livesHow it differs
SessionHeld per user by the serverIt's the remembering side. That said, keeping session records in storage every server reads rather than in one server's memory lets you use sessions and still satisfy the stateless condition

Where to go next

  • Backend

    see which area this design decision belongs to

  • Session

    the opposite approach, where the server does the remembering

Last updated 2026-09-16