Stateful vs. Stateless: What’s the Difference?
Stateful systems retain context between requests; stateless systems handle each request independently. Learn the trade-offs, scaling patterns, and examples.

Stateful systems remember context between interactions. Stateless systems handle each request independently. That distinction affects session handling, load balancing, scaling, failure recovery, latency, and operating cost. A stateful service may keep context in process memory, a connection, a session store, or a database. A stateless service does not require a particular server to remember a previous request; the client request or a shared data store supplies whatever context is needed.
HTTP itself is stateless by design. Applications can still create stateful sessions with cookies, server-side session records, WebSockets, or other mechanisms. A production architecture often combines both approaches: stateless HTTP instances in front of shared databases and caches, plus stateful components where a long-lived connection or workflow requires continuity.
What stateful means
A component is stateful when a later operation depends on information retained from an earlier operation. The retained information is the state. It can live in:
- Process memory on one application instance.
- A connection, such as a WebSocket or database session.
- A server-side session store, database, cache, or object store.
- A workflow engine that records progress between steps.
For example, a shopping cart stored in a server process is stateful. The next request must reach the process that owns that cart, or the cart must be replicated elsewhere. A WebSocket server is also stateful because it maintains an open connection and interaction context for the session.
What stateless means
A stateless component processes each request without depending on data held in one particular instance’s local memory or disk. The request contains the authentication and resource information required to authorize and fulfill it, or the service retrieves shared state from a database or cache. Any healthy instance can therefore handle the request.

Stateless does not mean “no storage.” AWS guidance recommends either avoiding per-instance state or offloading it so requests do not depend on local disk or memory. Shared databases, caches, queues, and object stores are compatible with a stateless API because they are not tied to one replaceable instance.
Stateful vs. stateless at a glance
| Concern | Stateful design | Stateless design |
|---|---|---|
| Session continuity | Context is retained by a process, connection, or session store. | Context travels with the request or is read from shared storage. |
| Load balancing | May need session affinity (“sticky sessions”) or replicated state. | Requests can go to any healthy instance. |
| Horizontal scaling | Scaling requires moving, sharing, or synchronizing state. | Adding or removing instances is usually simpler. |
| Failure recovery | Loss of an instance can lose in-memory context unless replicated. | A replacement instance can serve the next request when shared data remains available. |
| Long-lived connections | Natural fit for WebSockets and connection-oriented workflows. | Usually uses polling, queues, or a separate stateful connection layer. |
| Latency | Local memory can be fast; synchronization can add delay. | Shared-store reads add network latency; caching can reduce it. |
| Implementation | Simple conversational logic, more operational coupling. | Clear request contracts, more explicit context handling. |
Is HTTP stateful or stateless?
HTTP is stateless at the protocol level. MDN describes it as having “no link between two requests being successively carried out on the same connection.” A server does not automatically know that two requests came from the same user.

Applications add state above HTTP. A login flow commonly sets a cookie containing a session identifier. The browser sends that cookie on later requests, and the server looks up the corresponding session record. The protocol remains stateless; the application now has a stateful session model. See the MDN HTTP overview for the protocol behavior and session mechanisms.
REST statelessness
REST treats statelessness as a constraint: every request must be understandable and fulfillable without relying on the history of requests handled by a specific server. A request should carry authentication, the resource identifier, the operation, and any representation or control data needed for that operation.
A bearer token in an Authorization header is compatible with a stateless REST endpoint. The token may refer to a user record in a shared identity system, but the API instance does not need a private in-memory login session. A request such as GET /orders/123 can be routed to any healthy instance.
Runnable examples
Stateless Python HTTP endpoint
This Flask example authenticates each request with a bearer token and reads order data from a shared database abstraction. There is no per-process user session.
from flask import Flask, request, jsonify
app = Flask(__name__)
ORDERS = {
'123': {'id': '123', 'status': 'paid', 'total': 4200}
}
TOKENS = {'token-demo': 'user-42'}
def current_user():
header = request.headers.get('Authorization', '')
scheme, _, token = header.partition(' ')
if scheme.lower() != 'bearer' or token not in TOKENS:
return None
return TOKENS[token]
@app.get('/orders/<order_id>')
def get_order(order_id):
user_id = current_user()
if user_id is None:
return jsonify(error='unauthorized'), 401
order = ORDERS.get(order_id)
if order is None:
return jsonify(error='not found'), 404
return jsonify(order=order, user=user_id)
if __name__ == '__main__':
app.run(port=8000)
Run it with pip install flask, then start the file and call curl -H 'Authorization: Bearer token-demo' http://localhost:8000/orders/123. In production, replace the in-memory dictionaries with a shared database or cache. The request contract remains stateless even though the data is persistent.
Stateful Node.js session
This small Node.js server keeps sessions in process memory. It demonstrates why routing and failure behavior differ from the previous example.
import http from 'node:http';
import crypto from 'node:crypto';
const sessions = new Map();
const server = http.createServer((req, res) => {
if (req.url === '/login' && req.method === 'POST') {
const id = crypto.randomUUID();
sessions.set(id, { userId: 'user-42', createdAt: Date.now() });
res.setHeader('Set-Cookie', `sid=${id}; HttpOnly; SameSite=Lax`);
res.end('logged in');
return;
}
if (req.url === '/me') {
const cookie = req.headers.cookie || '';
const sid = cookie.split(';').map(v => v.trim()).find(v => v.startsWith('sid='))?.slice(4);
const session = sid ? sessions.get(sid) : undefined;
if (!session) { res.statusCode = 401; res.end('unauthorized'); return; }
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(session));
return;
}
res.statusCode = 404;
res.end('not found');
});
server.listen(8000);
Restarting this process destroys all sessions. Running two copies behind a load balancer also means a login request can reach one instance while /me reaches another. Fix that with a shared session store, replicated state, or a load balancer affinity policy with an explicit failure plan.
Equivalent cURL requests
curl -i -X POST http://localhost:8000/login
curl -i --cookie 'sid=YOUR_SESSION_ID' http://localhost:8000/me
curl -i -H 'Authorization: Bearer token-demo' http://localhost:8000/orders/123
How to choose an approach
- Start with the interaction. Use stateful behavior when a persistent connection, conversational workflow, or transaction context is central to the feature.
- Define the failure boundary. Decide what happens when an instance, connection, cache, or database disappears.
- Choose where state lives. Keep only short-lived, disposable data in process memory. Put durable or shareable state in a replicated database, cache, queue, or object store.
- Make request context explicit. For stateless APIs, include authentication, idempotency keys, resource identifiers, pagination cursors, and version information as needed.
- Measure the real bottleneck. A stateless API can still be slow if every request performs an uncached database query. A stateful process can still scale when connections are partitioned and state is replicated.
Common hybrid architectures
Stateless API plus shared session store: HTTP instances validate a cookie or token and load session data from Redis or a database. Any instance can serve the request.
Stateless command API plus stateful worker: The API accepts an idempotent job request and places it on a queue. A worker keeps workflow progress and emits events. Clients poll a status endpoint or subscribe through a separate connection.
Stateless edge plus stateful WebSocket service: REST endpoints handle authentication and resource operations while a WebSocket layer maintains live subscriptions. AWS API Gateway documents support for both stateful WebSocket APIs and stateless HTTP and REST APIs.
Client-held state: A signed token or client-side cursor carries limited context. Keep tokens small, rotate keys, set expiration, and avoid placing secrets or mutable authoritative data in them.
Scaling, reliability, and cost considerations
Stateless instances are easier to add, replace, and distribute across zones because the load balancer does not need to find the instance that owns a session. They also simplify autoscaling and blue-green deployments. The shared database, cache, or identity service becomes a dependency that needs capacity planning, replication, backups, and timeouts.
Stateful services can reduce repeated lookups when context stays in memory and can make streaming interactions straightforward. They require connection draining, session migration or replication, affinity rules, and careful rolling-deployment procedures. A process restart, node replacement, or network partition must have a defined user-visible result.
Cost follows the whole dependency graph. Stateless compute may scale down cleanly, while a shared cache or database may become the largest fixed cost. Stateful connection services can consume memory and file descriptors even when request volume is low. Track connection counts, session-store operations, cache hit rates, replication lag, queue depth, and retry volume rather than comparing compute prices alone.
Troubleshooting checklist
Users are logged out after deployment
Cause: Sessions live in process memory and the deployment replaced instances. Fix: Use a shared session store, persist sessions, or move to a stateless token model with appropriate expiration and revocation.
Intermittent “session not found” errors behind a load balancer
Cause: Requests reach different instances that do not share state. Fix: Share the state, configure affinity temporarily, and verify that the design survives an instance failure without affinity.
Autoscaling does not improve latency
Cause: Requests are waiting on a saturated database, cache, or connection pool. Fix: Inspect dependency latency and pool limits, add caching where safe, and apply backpressure instead of adding only API instances.
Duplicate payments or jobs appear after retries
Cause: A stateless endpoint repeats a side effect when the client times out. Fix: Require an idempotency key, store the operation result durably, and return the same result for retries.
WebSocket clients reconnect but lose context
Cause: Context existed only in the old connection or process. Fix: Persist resumable state, issue a connection or stream cursor, and define replay behavior after reconnect.
Cookies work in a browser but not in a script
Cause: The script does not preserve the Set-Cookie value or omits domain, path, Secure, or SameSite requirements. Fix: Use a cookie jar, inspect response headers, and send the cookie on subsequent requests over the correct origin.
Or skip the browser setup
If your practical goal is a clean image of a page rather than building and operating a browser capture service, ScreenshotNeo provides one GET request that returns PNG, JPEG, WebP, or PDF. The request is independently repeatable, while ScreenshotNeo handles browser state and cleanup for you.
See the ScreenshotNeo documentation for all options. A minimal call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://stripe.com'}, timeout=90)
open('shot.webp', 'wb').write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers identify the page verdict and whether it was billed. The MCP server includes take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Can a REST API keep session state?
Yes. REST statelessness means each request is independently understandable to the server. A cookie can identify a server-side session, provided the request can retrieve that session from shared storage.
Is JWT authentication always stateless?
The API can validate a self-contained token without local session memory, but revocation lists, refresh tokens, device records, and user profiles still introduce shared state.
Are databases stateful?
Databases retain state by definition. An API can remain stateless while using a database, as long as request handling does not depend on one API instance’s private memory or disk.
Should every service be stateless?
No. Use stateful components where persistent connections or continuity provide real value. Isolate them, replicate important state, and document recovery behavior.
What is the simplest migration path?
Externalize sessions first, remove instance-specific files, add idempotency for side effects, and make health checks and shutdown handling safe for replacement. Then test requests while instances are added, removed, and restarted.
Key takeaways
- Stateful means later operations use retained context.
- Stateless means any healthy instance can process a request using request data and shared services.
- HTTP is stateless, while cookies and server-side sessions add application state.
- Stateless designs simplify horizontal scaling; stateful designs simplify long-lived, conversational interactions.
- Hybrid systems are common and often the most practical choice.