AlgoInsight

System Design

Stateful vs Stateless Architecture

Whether your servers remember anything between requests sounds like a minor detail. It's actually one of the most consequential decisions in system design - and it's why stateless usually wins at scale.

By Admin2026-06-1813 min readstatefulstatelessarchitecturesystem-designscalabilitybackend

Here's a question that sounds trivial and turns out to be one of the most important in system design: does your server remember anything about a client between requests? If yes, it's stateful. If each request is handled fresh, with no memory of what came before, it's stateless. That single property cascades into how easily your system scales, how it recovers from failure, and how complex it is to operate.

For modern systems built to scale, stateless architecture has become the strong default - and understanding why reveals a lot about how large systems work. This guide explains both, shows what "state" actually means with concrete examples, and explains why "make it stateless" is one of the most common pieces of system design advice you'll hear.

What "state" means

In this context, state is information the server holds about a client's ongoing interaction - context remembered between separate requests. The classic example is a login session: after you log in, the server "remembers" that you're logged in, so you don't have to send your password with every single request.

The question is where that memory lives, and that's what separates the two architectures:

graph TB
    subgraph SF[Stateful: server remembers]
        C1[Client] --> S1[Server<br/>holds session<br/>in memory]
    end
    subgraph SL[Stateless: server forgets]
        C2[Client<br/>sends token<br/>every request] --> S2[Server<br/>holds nothing]
        S2 --> EX[(Shared State Store<br/>cache / database)]
    end
This diagram contrasts the two architectures. In the stateful model, a client talks to a server that holds the session in its own memory. In the stateless model, the client sends a token with every request, the server holds nothing itself, and any shared state lives in an external store like a cache or database that all servers can access.

The problem with stateful servers at scale

Stateful design feels natural - of course the server should remember you. And for a single server, it works fine. The trouble appears the moment you have more than one server, which is true of every system built to scale.

Imagine you have three servers behind a load balancer, and a stateful design where each server remembers its clients' sessions in memory. You log in, and the load balancer happens to send that request to server A. Server A now remembers you're logged in. But your next request might get routed to server B - which has never heard of you, has none of your session in its memory, and treats you as a stranger. You appear logged out. The system is broken.

There are two ways to patch this, and both have serious downsides:

Both patches fight against the very scalability and resilience you're trying to achieve. That's the core problem: state stored inside a server ties clients to that specific server, which is exactly what you don't want when you're trying to spread load freely and survive server failures.

Warning: Stateful servers and horizontal scaling are in tension. The more you scale out (more servers), the more painful in-server state becomes - because state living in one server's memory means clients can't freely move between servers, and a server failure destroys whatever state it held. This tension is why the stateless approach has become dominant for scalable systems.


The stateless solution: move state out

The elegant fix is to make the servers themselves hold no client state, and instead keep state in one of two places: carried with the request, or stored in a shared external store that all servers can reach.

Carry state with the request. Instead of the server remembering you're logged in, you prove it on every request. After logging in, you receive a token (often a JSON Web Token) that encodes your identity, and you send it with every subsequent request. The server validates the token and knows who you are - without having remembered anything. Any server can validate the token, so any server can handle any of your requests.

# Stateful approach: server remembers sessions in its own memory
sessions = {}   # lives in THIS server's memory only

def login_stateful(user):
    session_id = f"sess_{hash(user) % 10000}"
    sessions[session_id] = user           # server must remember this
    return session_id

def handle_stateful(session_id):
    user = sessions.get(session_id)        # only works on the SAME server
    return f"Hello {user}" if user else "Who are you? (unknown to this server)"

# Stateless approach: the request carries everything needed
def handle_stateless(token):
    # the token itself proves identity - ANY server can verify it
    user = token.replace("token:", "")     # (real systems use signed JWTs)
    return f"Hello {user} (handled by any server, no memory needed)"

sid = login_stateful("alice")
print("Stateful, same server:", handle_stateful(sid))
print("Stateful, different server:", "Who are you?")  # other server has no session
print("Stateless, any server:", handle_stateless("token:alice"))

The stateful session only works on the server that created it; a different server doesn't recognize it. The stateless token works on any server, because the request itself carries the proof. That's the whole advantage in one example.

Store state in a shared external store. For state that can't fit in a token - a shopping cart, a multi-step workflow - keep it in a database or a distributed cache (like Redis) that all servers can access. The servers stay stateless; the state lives in the shared store. Any server can pick up any request and look up the needed state from the same central place. The state still exists - it's just moved out of the individual servers into a layer they all share.


Why stateless wins for scale

Once servers hold no client state, the benefits cascade:

This is why "make your services stateless" is one of the most common and durable pieces of system design advice. Statelessness is what makes horizontal scaling and high availability easy instead of painful. (It's also a core principle of REST APIs, which require each request to be self-contained for exactly these reasons.)


When stateful still makes sense

Stateless isn't universal - some things are inherently stateful, and forcing them to be stateless is its own mistake. Databases are stateful by nature: their entire job is to remember data. Real-time connections like WebSocket are stateful - an open connection is state held on the server. Some specialized systems (certain games, stateful stream processors) genuinely need to hold state in memory for performance.

The modern pattern handles this cleanly: push the state into dedicated stateful components (databases, caches) and keep the application servers stateless. The application layer - the part you scale most aggressively - stays stateless and easy to scale, while the stateful concerns are concentrated in components specifically designed to manage state reliably. You don't eliminate state; you relocate it to where it's handled best.

Tip: The goal isn't "no state anywhere" - that's impossible, since some component must remember things. The goal is keeping your application servers stateless so they're easy to scale and replace, while concentrating necessary state in purpose-built stateful systems (databases, distributed caches) that are designed to manage it. Separate the stateless workhorses from the stateful stores.


A concrete example: the shopping cart

Nothing makes the distinction clearer than a real feature: an online shopping cart. A user adds items, browses more, and eventually checks out - across many separate requests. Where does the cart's contents live between those requests?

The stateful approach: the server that handled "add item" keeps the cart in its own memory. This works as long as every one of the user's requests goes back to that same server. But behind a load balancer, the next request might hit a different server that has no idea what's in the cart - so the cart appears empty. To make it work, you'd need sticky sessions (pinning the user to the original server) or session replication (copying the cart to every server), both with the downsides we covered. And if that server crashes, the cart is gone.

The stateless approach: the servers hold no cart in memory. Instead, the cart lives in a shared store - a database or a distributed cache like Redis - keyed by the user. Any server handling any of the user's requests reads and updates the cart in that shared store. Now it doesn't matter which server gets the request; they all see the same cart. Add a server, lose a server - the cart is unaffected, because it never depended on any single server's memory.

The second approach is why your cart survives even as your requests bounce across a fleet of servers you never see. The state exists - it just lives in a shared store designed to hold it, not in the application servers.

Sessions, tokens, and where state lives

It's worth being precise about the options for handling user identity statelessly, since it's the most common case:

Both keep the application servers stateless. They differ in whether the state rides inside the request (token) or sits in a shared store the servers consult (session ID). Either is a sound stateless design; the choice depends on whether you prioritize zero-lookup speed or easy revocation.

"Stateless" doesn't mean "no data"

A common confusion: stateless doesn't mean the system forgets everything or stores nothing. Of course your application has data - user accounts, orders, content. "Stateless" refers specifically to the application servers not holding client-session context between requests in their own memory. The data absolutely persists - it just lives in databases and caches, the components designed to store it, rather than in the memory of the servers handling requests. A stateless server can read and write all the data it needs from those stores; it simply doesn't remember anything itself once a request is done. The distinction is about where state lives - in dedicated stores accessible to all servers, versus in one server's private memory accessible only to that server. Keeping that straight resolves most of the confusion around the term.

This principle scales up to microservices too: each service is typically designed to be stateless, holding its data in its own datastore rather than in memory, so each service can be scaled and replaced independently. Statelessness isn't just a single-service tactic - it's an architectural discipline that keeps an entire system flexible.


Quick comparison

Stateful Stateless
Server memory between requests Holds client context Holds nothing
Where state lives In the server In the request or a shared store
Load balancing Needs sticky sessions Any server, any request
Horizontal scaling Hard (state ties clients to servers) Easy (servers interchangeable)
Server failure Session lost No session to lose
Complexity Higher (replication/affinity) Lower (servers identical)
Good for Databases, real-time connections Application/API servers

Why it matters in interviews

Interview: When designing a system, defaulting your application servers to stateless is a strong, expected move - and explaining why shows real understanding. A great line: "I'll keep the app servers stateless and store session state in a shared cache like Redis, so any server can handle any request. That way I can scale horizontally freely and a server failure doesn't lose anyone's session." If an interviewer brings up sticky sessions, recognizing them as a workaround for stateful servers - and preferring the stateless approach - signals you understand the deeper trade-off.


The bottom line

Whether your servers remember clients between requests - stateful - or treat each request fresh - stateless - is a foundational design decision. Stateful servers feel natural but tie clients to specific servers, which makes scaling and failure recovery painful. Stateless servers hold no client context: each request carries what it needs (a token) or pulls it from a shared store (a cache or database), which lets any server handle any request, makes horizontal scaling effortless, and means a server failure loses nothing.

The winning pattern for scalable systems: keep your application servers stateless, and concentrate necessary state in purpose-built stateful stores. That separation is what lets modern systems scale to enormous size while staying resilient, and we build it across System Design Fundamentals.


Go deeper

More articles