KAHIBARO
Discord Login Register

26.10. Stateless Applications

Understanding Stateless Applications

In backend development, the idea of a "stateless application" shows up everywhere. It affects how you design APIs, sessions, caching, and how you scale your system. This chapter focuses on what statelessness means, why it matters, and how to design for it in practice.

What “State” Means in Backend Systems

When we say "state" in backend systems, we usually mean data that changes over time and affects how the system behaves.

Examples of state:

Some of this state is short-lived (for example, which page of results you are on), some is long-lived (for example, user profile data).

The key question for statelessness is: Where is this state stored and who remembers it between requests?

Stateless vs Stateful Applications

Stateless application

A stateless application does not remember anything between requests. Every request is handled as if the server had never seen that client before.

Stateful application

A stateful application keeps some in-memory context about the client between requests.

Examples:

These servers "remember" things without needing all of it to be sent again by the client.

Important rule:
A stateless server must be able to handle any valid request without relying on any in-memory data that was created by previous requests from the same client.

Why Stateless Applications Matter for Scalability

Stateless applications fit very well with horizontal scaling and load balancing.

Easier load balancing

In a stateless design:

Since no server keeps private memory about that user, any server can serve any request.

In a stateful design, you often need "sticky sessions" where the same user must always be routed to the same server, because that server holds the user’s in-memory session. This makes scaling and failover more complicated.

Easier to add or remove servers

With statelessness:

You do not have to copy over in-memory session data to the new servers. All necessary state is outside the servers, for example in:

Better fault tolerance

If one stateless server crashes midway:

In a stateful server:

Statelessness and REST APIs

REST (Representational State Transfer) encourages stateless communication between client and server.

In a RESTful system:

Example:

A stateful design might:

  1. POST /login
    Server stores user_id = 123 in an in-memory session associated with a session cookie.
  2. GET /orders
    Server reads user_id from memory and returns orders for user 123.

A stateless design might:

  1. POST /login
    Server returns a JWT token encoding user_id = 123.
  2. GET /orders with Authorization: Bearer <token>
    Server decodes the token on every request, without needing any in-memory session.

All the necessary info (the token) is sent with each request.

Where to Keep State in a “Stateless” System

Even in a “stateless application,” the system as a whole is not state-free. You still need to persist data. The idea is to move state away from individual app server instances.

Common storage places:

Type of stateTypical storage
User accountsDatabase (for example, PostgreSQL)
Shopping cartsDatabase or cache (for example, Redis)
AuthenticationJWTs, token tables in DB, or Redis
Rate limitsRedis counters or similar
Background job progressJob queue database or Redis
Long-term business dataRelational or NoSQL databases

The app server simply:

The server itself does not "own" state. It just uses it.

Practical Example: Stateless Session Handling

Imagine a simple login system.

Stateful session example

  1. Client sends POST /login with username and password.
  2. Server:
    • Verifies password.
    • Generates a random session ID like abc123.
    • Stores { session_id: "abc123", user_id: 42 } in memory or an in-process data structure.
    • Sends back a cookie: Set-Cookie: session_id=abc123.
  3. Client later requests GET /profile with Cookie: session_id=abc123.
  4. Server:
    • Looks up session in memory by abc123.
    • Finds user_id = 42.
    • Returns user 42’s profile.

This server is stateful, because the in-memory session is required for future requests.

Stateless token example

  1. Client sends POST /login with username and password.
  2. Server:
    • Verifies password.
    • Generates a JWT with payload { "sub": 42, "exp": <timestamp> }, signed with a secret key.
    • Returns: { "access_token": "<jwt-here>" }.
  3. Client later requests GET /profile with header Authorization: Bearer <jwt-here>.
  4. Server:
    • Verifies the JWT signature and expiration using its secret key.
    • Reads "sub": 42.
    • Returns user 42’s profile.

No in-memory session is required. Any server instance can decode the token and process the request.

Common Patterns to Achieve Statelessness

1. Use tokens, not in-memory sessions

Instead of storing sessions in memory:

Example pattern:

The server can remain stateless if it only needs to verify tokens, not maintain per-user memory.

2. Store user-specific state in a database or cache

Do not keep things like cart contents in RAM per user.

Instead:

text
  carts
  -----
  user_id
  item_id
  quantity

In both cases, any server can load and update that cart.

3. Make requests self-contained

Each request should include:

Avoid designs where:

Bad pattern:

  1. POST /search with body { "query": "laptop" }.
    Server stores last_search = "laptop" in memory.
  2. GET /search/next-page.
    Server uses last_search from memory.

Better stateless pattern:

Now the second request contains all needed information.

4. Use caches that are external to the app instance

You can cache data and still be stateless, as long as:

Avoid per-instance in-memory caches that hold user-specific or request-specific state that other instances cannot see.

When Full Statelessness Is Hard or Undesirable

Not every system can be perfectly stateless.

Examples:

In these cases, you try to:

Example: Migrating a Stateful API to Stateless

Imagine you have a simple note-taking API with this flow:

  1. Client logs in with POST /login and gets a session cookie.
  2. Server stores the user in a global sessions dict, keyed by session ID.
  3. GET /notes reads the user from that dict.
  4. POST /notes creates notes for that user.

You want to scale to multiple app servers, but your in-memory sessions dict is tied to one instance.

Step 1: Move sessions to a shared store (intermediate)

Use Redis instead of in-memory dict:

Now any instance can read the session from Redis. The application is still logically stateful from the protocol point of view, but the state is no longer tied to one server.

Step 2: Switch to tokens (more stateless)

Replace session cookies with access tokens:

Now you can:

From the API client’s perspective, the protocol is now stateless. Every request can be processed alone.

Trade-offs of Stateless Designs

Choosing statelessness has both benefits and costs.

Benefits

Costs and considerations

Often, systems end up mostly stateless, with a few carefully isolated stateful components.

Summary

Views: 8

Comments

Please login to add a comment.

Don't have an account? Register now!