Asher Cohen
Back to posts

🔁 REST is stateless

That doesn’t mean your app is

There’s this recurring confusion in backend discussions:

“If REST is about Representational State Transfer, how can it be stateless? Aren’t we literally transferring state?”

Let’s break it down.


💡 Stateless ≠ No State

Stateless means:

The server doesn’t store per-client memory in RAM between requests.

Back in the early web days, we used server-side sessions (in-memory) to track users across requests. That made servers stateful and tightly coupled user flows to a specific machine.

That broke things:

Couldn't scale horizontally (sticky sessions, ugh)

Crash a node? Lose user progress

Caching and retries were unpredictable

So we learned to externalize state — usually into a DB or cache — and make each request self-contained.


🔄 So what does “State Transfer” mean?

REST doesn’t mean "no state" — it means state lives on the client.

The client sends all context needed to process a request The server responds with the current representation of resource state

That’s the “transfer” part — the client drives the flow.


🧱 Example: onboarding wizard

Say you build a 4-step onboarding flow:

POST /onboarding => { flowId: "abc123" } PUT /onboarding/abc123/step/1 => { personalInfo } PUT /onboarding/abc123/step/2 => { contractDetails }

✅ The server never remembers your session ✅ It uses flowId to load and persist state ✅ It’s stateless, but still supports stateful flows


🧠 Why this matters

Storing session state in server memory locks you in:

Hard to autoscale

Hard to recover from failures

Hard to decouple services

This is why statelessness became a best practice. Not because it’s “clean” — but because it’s scalable, resilient, and plays well with modern infra.


⚠️ Gotchas

Stateless API ≠ no persistence — Your DB is the server’s long-term memory.

You can break the rule (e.g. multiplayer, WebSockets) — but that requires strong coordination (e.g. distributed state).


🗂️ Real-world pattern

You want to persist where users are in a workflow?

Don’t track it in RAM. Persist it:

  1. In a backing store (Postgres, Redis, etc.)

  2. Or encoded in a token/URL

Client sends a flowId or token in each request. Server stays stateless — app stays stateful.


🧩 The takeaway

Stateless server ≠ stateless app

State transfer = client includes the context in every request

You get robustness, scaling, and clarity — without giving up complex flows


🎯 TL;DR

  1. Stateless: Server holds no in-memory session

  2. State transfer: Client manages context, server processes statelessly

  3. Modern web flows: Use DBs or tokens, pass IDs in every call — and scale without fear