R1.2 — Where state lives: stateful services, stateless services and hybrid systems

Part of R1. The story in R1 relied on one property: any copy of the payment service could take the request. This part explains where state lives so that this is true, and what to do when it cannot be.

Every service has state. The design question is never "does it have state" — it is where the state lives between requests, and what happens to it when the machine holding it dies.

Stateful: the server remembers

A stateful server keeps the client's state in its own memory between requests: the session, a game position, the open order book.

You pick this when latency is non-negotiable. A multiplayer game updates its match state 50 to 60 times a second; a chat server holds millions of open WebSocket connections; an exchange matching engine matches in microseconds. A network hop to a shared store on every one of those would be the bottleneck.

The price is that the client must keep coming back to the same server. That is sticky sessions (session affinity) at the load balancer, done one of three ways:

Method How it works Catch
Cookie-based The load balancer adds a cookie naming the server New servers get no traffic until new sessions start
Session-ID-based The server's identity is a suffix in the session ID (Tomcat JSESSIONID + jvmRoute) The load balancer keeps no table, but you are tightly coupled to the ID format
IP-based Hash the client IP to a server Clients behind one NAT all land on one server — R14 covers IP hash

And the drawbacks follow from the pinning:

Stateless: the server keeps nothing between requests

A stateless server keeps nothing in its own memory between one request and the next. Whatever the next request needs about this client — who they are, which step of a flow they are on — comes from somewhere other than the server that happens to receive it.

Any server can serve any request. That one property buys almost everything:

What "stateless" does not mean is "has no data". A stateless payment service writes payments to Postgres on every call, and it is still stateless: the term is about the conversational state between one client's requests — the session — not about whether the system persists anything. The Twelve-Factor App puts it the standard way: processes are stateless and share nothing, and any data that must persist lives in a stateful backing service such as a database.

So for a stateless service the only design question left is where the session lives, and there are two answers:

Session in the request Session in a shared store
What the client sends A signed token (JWT) carrying user, roles and expiry An opaque session ID
Work per request Verify a signature — CPU only, no network call A lookup in Redis or a database
New dependency None The store, which must be highly available
Logout and revocation Hard: the token is valid until it expires, so keep it short-lived Instant: delete the session
What it can hold Small, mostly fixed claims, readable by the client (signed, not encrypted) Anything, of any size, changing mid-session — a cart, a completed MFA step
Typical tools JWT, OAuth 2.0, OpenID Connect Spring Session on Redis, DynamoDB, a SQL table

Both are stateless services. The second does not make the server stateful; it moves the session off the server into a store every instance can reach. Real systems usually combine them: a short-lived JWT so most requests need no lookup, and a refresh token or session record in the store so access can be revoked.

Keeping the session in a shared store

The shared store is where most of the operational cost of a stateless design ends up, so it is worth naming:

What you gain What you pay
Scaling Free scale-out; app and store scale independently Extra latency on every request that needs the session
Failure A server crash loses nothing The store is a new single point of failure if it has no failover
Operations Easier deploys, nothing to drain More infrastructure to run; multi-region consistency of the store

The practices that make it safe: run the store redundantly, back it up, pool connections to it, and put a TTL on every session so abandoned ones expire on their own.

Store choice is the usual trade: Redis gives microsecond latency, native TTL and replication; a cloud-managed Redis removes the operating burden; SQL is durable but slower; MongoDB or DynamoDB give a flexible shape.

Hybrid systems: stateful and stateless services side by side

Stateful and stateless describe a service. "Hybrid" describes a system that runs both kinds deliberately: a large stateless tier that absorbs scale, and a few stateful services where holding state in memory is the whole point.

Start stateless; keep the session in the request or a shared store; add a stateful service only for a clear real-time case, and keep it small, isolated and rebuildable.

The rules that make a hybrid system work:

Where real systems put their state

The categories are easiest to defend with examples. Learn a few from each table.

Stateless, session in the request or none at all — every instance is interchangeable.

System Why stateless works Where the state actually lives
Public REST APIs (Stripe, GitHub) Each call carries an API key or token; any instance can answer The database; idempotency keys are stored server-side
Payment-initiation API Request in, 202 and a payment ID out; the payment row and idempotency record are the only memory Payments database, idempotency table (R3)
Payment validation (IBAN/BIC checks, field validation, enrichment) Request in, verdict out; scale with message volume Reference data in a database or cache
Serverless functions (AWS Lambda) Instances appear and vanish per request DynamoDB, S3, queues
Image resizing / thumbnail services A pure function: input to output Object storage + CDN
Search query tier, GraphQL/BFF layers Fan out to backends, no memory between calls The search index, downstream services

Stateless, session or shared counters in a fast store — the servers still hold nothing.

System Pattern Shared store
Banking web and mobile login Short-lived stateless JWT access token, plus a server-side refresh token or session so it can be revoked Redis or a database
E-commerce login sessions and carts Spring Session or a server-side session in a store; any node serves the user Redis, DynamoDB (Amazon's Dynamo paper was motivated by the shopping cart)
Payment workflow orchestration Stateless workers pick up the next step; workflow state is persisted Workflow engine database (Temporal, Camunda), outbox tables (R4)
API rate limiting Stateless gateways share counters Redis INCR + TTL
Netflix microservices Stateless services + a replicated cache for per-member data EVCache (memcached-based)
Ride-hailing driver locations A stateless API writes GPS pings; matching reads nearby drivers Redis geo sets

Stateful — the state in memory is the product.

System Why it must be stateful How it copes with the downsides
Exchange matching engines (e.g. the LMAX architecture) The order book is in memory for microsecond matching Single-threaded engine + event journal; replay to rebuild; hot standby
Multiplayer game servers Authoritative match state updated many times a second One match per server process; players reconnect to the same server
Chat and presence (WhatsApp on Erlang, Discord on Elixir) Millions of open connections; per-room state in memory Consistent hashing to place rooms (R10); processes restart and rehydrate
Collaborative editing (Google Docs, Figma) One server holds the live document and merges edits Per-document routing; periodic checkpoints to storage
Video calls (WebRTC SFU media servers) Per-call media routing state The call is pinned to one server; clients reconnect on failure
Stream processing (Flink, Kafka Streams) Windows, joins and aggregates kept in local state Checkpoints and changelog topics for recovery

Read the third column of the stateful table again. Every stateful system that survives in production has a way to rebuild its memory — a journal, a checkpoint, a changelog. Stateful does not mean "the state exists only in RAM"; it means the hot copy is in RAM and the durable copy is somewhere you can replay from.

Hybrid systems — a stateless tier around a few stateful services.

System Stateless services Stateful services
WhatsApp, Discord Profile, contacts and media-upload APIs Connection and chat servers holding open sockets and room state
Exchange or FX trading platform Order entry, validation and pre-trade risk checks The matching engine holding the order book
Uber Trip, pricing and payment APIs In-memory supply and location indexes sharded by geography (Uber built Ringpop to route to them)
Google Docs Document listing, sharing and permissions APIs One collaboration server per live document, merging edits
Online games Matchmaking, store and profile APIs Match servers holding authoritative game state
Payments platform Initiation, validation and status APIs Stream processors keeping rolling fraud features and real-time limit counters (Flink)

The trap to name: "our login is stateless because we use JWTs." A pure JWT cannot be revoked before it expires, because nobody is asked. Banking logins pair a short-lived JWT with a refresh token held server-side precisely so that a stolen phone can be cut off. The service is still stateless — the revocable part of the session lives in a shared store — and saying so is the senior answer.

Before reading on, step through the walkthrough below. It starts with a stateful server behind sticky sessions, crashes it, and shows the sessions vanishing. Then watch the same traffic served by stateless JWTs, and finally by stateless servers over an external session store — and note which step survives the crash and why.

The defensible position: name the state, then say where it lives. "The payment API is stateless; sessions and refresh tokens are in Redis with a TTL; the matching engine is stateful, journaled, and rebuilt by replay." That sentence describes a hybrid system: stateless services everywhere except the one place where memory is the product.

What to say in the interview

"The payment API is stateless — any instance serves any request, and the state lives in the payments database, the idempotency table and a Redis session store with a TTL. I keep stateful components, like a matching engine, for genuine real-time cases, and journal them so they can be rebuilt after a crash."