When designing a web application, an important question is:
Where should the user's state or session information be stored?
The answer helps us understand the difference between stateful and stateless services.
What Is State?
State is information that a system needs to remember about a user or an ongoing operation.
For example:
- Logged-in user
- Shopping cart
- Current session
- Selected language
- Temporary workflow information
If a server stores this information locally and needs it to process future requests, the service is stateful.
What Is a Stateful Service?
A stateful service remembers information about the client between requests.
For example, suppose a user logs in to API Server 1.
flowchart LR
User --> API1
API1 --> UserSession
The server stores information about the user's session.
When the user sends the next request, the application expects to find that session information on the same server.
flowchart LR
User --> API1
API1 --> Session
User --> API1
This creates a dependency on API1.
Problem With Stateful Services
Now suppose we add a second server.
flowchart LR
User --> LoadBalancer
LoadBalancer --> API1
LoadBalancer --> API2
The first request may go to API1.
The next request may go to API2.
sequenceDiagram
User->>API1: Login
API1-->>User: Session created
User->>API2: Next request
API2-->>User: Session not found
API2 does not know about the session stored on API1.
This can create problems when scaling the application.
Session Affinity
One solution is to use session affinity, also called sticky sessions.
The Load Balancer tries to send the same user's requests to the same server.
flowchart LR
User1 --> LoadBalancer
LoadBalancer --> API1
User2 --> LoadBalancer
LoadBalancer --> API2
This can work, but it creates a dependency on particular application instances and can make scaling and failure handling more complicated.
What Is a Stateless Service?
A stateless service does not depend on information stored only in a particular application server between requests.
Each request contains the information required to process it, or the required state is stored in an external shared system.
flowchart LR
User --> LoadBalancer
LoadBalancer --> API1
LoadBalancer --> API2
API1 --> Database
API2 --> Database
Now the next request can go to either API1 or API2.
Both servers can process the request independently.
ASP.NET Core Example
Consider a simple API:
[HttpGet("profile")]
public IActionResult GetProfile()
{
var userId = User.FindFirst("sub")?.Value;
return Ok(new
{
UserId = userId
});
}
The API does not need to remember the user in the memory of one particular server.
The request contains authentication information, and the application can retrieve additional information from shared storage when required.
Where Should State Be Stored?
If the application needs state, it does not mean that we cannot use stateless application servers.
We can move shared state outside the application instance.
For example:
- SQL Server
- Redis
- Distributed cache
- Object storage
flowchart LR
User --> LoadBalancer
LoadBalancer --> API1
LoadBalancer --> API2
API1 --> Redis
API2 --> Redis
API1 --> Database
API2 --> Database
Now API1 and API2 can access the same shared state.
Example: Shopping Cart
Imagine a user adds a laptop to a shopping cart.
A stateful implementation might store the cart inside API1's memory.
flowchart LR
User --> API1
API1 --> LocalMemory
If the next request goes to API2, API2 cannot see the cart.
A better approach for a horizontally scaled application is to store the cart in shared storage.
flowchart LR
User --> LoadBalancer
LoadBalancer --> API1
LoadBalancer --> API2
API1 --> Redis
API2 --> Redis
Both API servers can access the same cart.
SQL Server Example
We could store a shopping cart in SQL Server:
CREATE TABLE CartItems
(
Id INT PRIMARY KEY IDENTITY,
UserId INT NOT NULL,
ProductId INT NOT NULL,
Quantity INT NOT NULL
);
The API can retrieve the cart using the user ID:
[HttpGet("cart")]
public async Task GetCart()
{
var userId = User.FindFirst("sub")?.Value;
var items = await _context.CartItems
.Where(x => x.UserId == userId)
.ToListAsync();
return Ok(items);
}
It does not matter which API server receives the request because the cart is stored in shared storage.
Authentication Example
Stateless authentication is commonly implemented using access tokens.
The client sends the token with each request.
sequenceDiagram
User->>API: Request + Access Token
API->>API: Validate Token
API-->>User: Response
The API does not need to remember the authentication state inside its own memory.
This makes it easier to send requests to different application instances.
Stateful vs Stateless
| Stateful | Stateless |
|---|---|
| Server remembers client state | Server does not depend on local client state |
| State may be stored in server memory | State can be stored externally |
| Scaling can be more complicated | Horizontal scaling is generally easier |
| Server affinity may be required | Requests can generally go to any instance |
| Server failure can lose local state | Shared state can survive individual instance failure |
Why Stateless Services Help Scaling
Consider three API servers.
flowchart LR
User --> LoadBalancer
LoadBalancer --> API1
LoadBalancer --> API2
LoadBalancer --> API3
If the APIs are stateless, any server can process any request.
When traffic increases, another server can be added.
flowchart LR
User --> LoadBalancer
LoadBalancer --> API1
LoadBalancer --> API2
LoadBalancer --> API3
LoadBalancer --> API4
The new server does not need to copy the in-memory state of the other servers.
Does Stateless Mean No State Exists?
No.
This is a common misunderstanding.
Stateless means the application server does not depend on its own local memory to maintain client state between requests.
The state can still exist in a database, Redis or another shared storage system.
flowchart LR
User --> API
API --> SharedState
SharedState --> Database
When Stateful Services Can Be Useful
Stateful services are not always bad.
Some systems naturally need state, such as:
- Real-time communication systems
- WebSocket connections
- Long-running workflows
- Specialized stateful processing
However, the architecture needs to account for how state is managed when instances are added, removed or fail.
Key Takeaways
- State is information that must be remembered between operations.
- Stateful services keep client-related state tied to an application instance.
- Stateless services do not depend on local server memory for client state.
- Stateless APIs are generally easier to scale horizontally.
- Shared state can be stored in SQL Server, Redis or another shared system.
- Sticky sessions can support stateful applications but introduce additional considerations.
- Stateless does not mean the system has no state.
Conclusion
Stateful services keep information associated with a particular application instance, while stateless services allow requests to be handled by different instances without depending on local server state.
For modern web applications, keeping API servers stateless and storing required shared state externally often makes horizontal scaling and failure handling easier.
The next question is how we organize the application itself: should everything be built as one application, or should it be divided into multiple services?
That brings us to the next concept: Monolith vs Microservices.