Horizontal vs Vertical Scaling
Making a server bigger vs adding more of them — and why statelessness is what makes the second option possible.
3 min read
When a backend can no longer handle its traffic comfortably, there are two fundamentally different ways to give it more capacity: make the existing server more powerful, or run more servers. These are vertical and horizontal scaling, and the choice between them shapes a lot of earlier architectural decisions, including statelessness and session design.
Vertical scaling
Vertical scaling (scaling up) means giving a single server more resources — more CPU, more RAM, faster disks.
Before: [ 4 CPU, 8 GB RAM ]
After: [ 16 CPU, 64 GB RAM ]
It's simple — no architectural changes needed, the application code doesn't need to know or care — but it has a hard ceiling (there's a biggest machine you can rent or buy) and a single point of failure: if that one server goes down, everything depending on it goes down with it.
Horizontal scaling
Horizontal scaling (scaling out) means running more instances of the server and distributing traffic across them, typically with a load balancer in front deciding which instance handles each request.
┌──────────────┐
Client requests → │ Load Balancer│
└──────┬───────┘
┌────────────┼────────────┐
▼ ▼ ▼
[ Server 1 ] [ Server 2 ] [ Server 3 ]
There's no practical ceiling in the same way — need more capacity, add more instances — and losing one instance doesn't take the whole system down, since the load balancer just stops routing to it. The cost is architectural: the application has to be designed so that any instance can handle any request.
Why statelessness is the prerequisite
This is where the request/response and sessions lessons connect directly to scaling. If a server keeps session state in its own local memory, a load balancer sending a user's second request to a different instance than their first would mean that instance has never heard of them — the user gets logged out or loses their cart, seemingly at random. Horizontal scaling only works cleanly when servers are stateless: any shared state (sessions, cached data) lives in something all instances can reach — a shared database, a shared Redis instance — not in one server's local memory.
Scaling the database is a different, harder problem
These patterns describe scaling stateless application servers, which is comparatively straightforward once statelessness is in place. The database underneath is a different story — it holds the actual state, so scaling it horizontally (sharding, read replicas) is significantly harder and involves real tradeoffs around consistency that scaling a stateless web server doesn't. Many systems scale their application layer horizontally to dozens of instances while their primary database stays a single, powerful (vertically-scaled) machine, with read replicas added only when read load specifically demands it.
Which to reach for
Vertical scaling is the pragmatic first move for a smaller system — it buys time with zero architectural work. Horizontal scaling is what serious production systems eventually need, both for capacity beyond what one machine can offer and for redundancy: a system that only runs one instance has no protection against that one instance failing, no matter how powerful it is.
Running many instances reliably is much easier with a consistent, portable way to package and run the application itself — which is exactly what containers provide, next.