Skip to main content
PProfileMatch

Scalability and Load Balancing

System Design & ArchitectureLesson 2 of 65 min read

What you'll learn

  • Compare vertical and horizontal scaling and know when each fits
  • Explain why stateless services scale and where session state should live
  • Choose a load balancing algorithm and explain Layer 4 vs Layer 7
  • Find and remove single points of failure, including the load balancer itself

On this page

  1. Vertical vs horizontal scaling
  2. Stateless services: the key to scaling out
  3. Load balancer algorithms
  4. Health checks and failure handling
  5. Single points of failure
  6. Common mistakes
  7. How this shows up in interviews and real projects

Your app works on launch day. Then a popular newsletter links to it, traffic grows tenfold in an hour, and the single server it runs on starts timing out. Scalability is about being ready for that moment, and load balancing is the first tool you reach for.

Vertical vs horizontal scaling

Scaling means handling more load. You can make one machine bigger (vertical scaling, or scale up) or add more machines (horizontal scaling, or scale out). Neither is universally better.

Vertical (scale up)Horizontal (scale out)
HowBigger CPU, more RAM, faster diskMore machines behind a load balancer
ComplexityLow: no code changesHigher: needs stateless design, coordination
CeilingHard limit: the largest machine you can buyPractically much higher
Failure impactOne machine is a single point of failureLosing one node is survivable
Cost curveGets expensive at the top endRoughly linear with commodity hardware
Best forEarly stage, databases that are hard to splitWeb and API tiers, anything stateless

A sensible path: begin vertically because it is simple, and move to horizontal scaling when you approach the limits or when you need redundancy. Horizontal scaling also gives you availability, because one failing machine no longer takes the whole service down.

Stateless services: the key to scaling out

A service is stateless when any server can handle any request, because nothing about the user's session lives only in that server's memory. If your server stores a shopping cart in local memory, then the user must always hit that same server, and if it dies the cart is lost.

To make a service stateless, push state out to shared systems. Keep the cart or session in a shared store such as Redis or in the database, or put it in a signed token (like a JWT) sent with each request. Each choice has a cost: a shared store adds a network call per request and must itself be made reliable, while tokens cannot be revoked as easily and grow with the data you place in them.

Load balancer algorithms

A load balancer receives requests and forwards each one to a healthy backend server. The algorithm decides which server gets the next request.

AlgorithmHow it worksGood whenWatch out for
Round robinRotate through servers in orderServers are identical, requests are similarIgnores current load and request cost
Weighted round robinServers with more capacity get more turnsMixed hardwareWeights must be kept up to date
Least connectionsPick the server with fewest active connectionsRequests vary in duration (uploads, streaming)Needs connection tracking
IP / key hashHash client IP or a key to choose a serverYou want affinity, such as cache localityUneven spread; reshuffles when servers change
Random with two choicesPick two at random, send to the less loadedLarge fleets, simple and surprisingly evenNeeds load info from backends

A load balancer can work at Layer 4 (TCP level: fast, only sees IPs and ports) or Layer 7 (HTTP level: can route by URL path, header or cookie, terminate TLS and retry requests, at the cost of more processing). Many systems use Layer 7 for the web tier because routing /api and /images to different server pools is so useful.

nginx.conf (excerpt)
upstream app_servers {
    least_conn;
    server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.13:8080 backup;
}

server {
    listen 80;

    location / {
        proxy_pass http://app_servers;
    }
}

The same idea in a few lines of TypeScript makes the logic concrete. This round-robin picker skips servers that are not in the healthy set.

roundRobin.ts
class RoundRobin {
  private index = 0;

  constructor(private servers: string[]) {}

  next(healthy: Set<string>): string {
    for (let tries = 0; tries < this.servers.length; tries++) {
      const server = this.servers[this.index % this.servers.length];
      this.index++;
      if (healthy.has(server)) return server;
    }
    throw new Error("no healthy servers available");
  }
}

Output

servers = [A, B, C], healthy = {A, B, C}  ->  A, B, C, A, B, C ...
servers = [A, B, C], healthy = {A, C}     ->  A, C, A, C ...

Health checks and failure handling

A load balancer is only useful if it stops sending traffic to broken servers. Active health checks probe each backend on a schedule (for example GET /health every 5 seconds) and remove it after several failures. Passive checks watch real traffic and mark a server unhealthy after repeated errors or timeouts, as in max_fails above.

  • Make /health reflect real readiness, such as being able to reach the database, but keep it cheap so it does not overload a struggling server.
  • Require several consecutive failures before removing a server, and several successes before adding it back, to avoid flapping.
  • Support graceful draining: stop sending new requests to a server being deployed, let in-flight requests finish, then restart it.
  • Capacity-plan with headroom. If you need 4 servers for peak, run 5 so that losing one (N+1) does not overload the rest.

Single points of failure

A single point of failure (SPOF) is any component whose failure takes the whole system down. After adding multiple app servers, the load balancer itself becomes one. Fix it with a redundant pair (active-passive with failover through a shared virtual IP, or active-active behind DNS), or by using a managed load balancer that is itself distributed.

Removing the load balancer SPOF
        Clients
           |
           v
    +-------------+
    | DNS / VIP   |
    +-------------+
       |       |
       v       v
  +------+  +------+
  | LB 1 |  | LB 2 |   active / standby
  +------+  +------+
       \       /
        v     v
  +-----+ +-----+ +-----+
  | App | | App | | App |
  +-----+ +-----+ +-----+
        \    |    /
         v   v   v
        +----------+
        | Database |  <-- next SPOF!
        +----------+

Notice the database at the bottom: scaling the stateless tier just moves the bottleneck and the SPOF downwards. The next lessons, Caching and Databases, address that.

Common mistakes

  • Storing sessions or uploads on local disk. Symptom: users are randomly logged out or files go missing when the load balancer picks a different server.
  • Scaling the app tier but not the database. Symptom: adding servers does not help because every request waits on the same overloaded database.
  • Forgetting the load balancer is a SPOF. Symptom: the whole site is down when one box fails, even though ten app servers are healthy.
  • Health checks that are too shallow or too aggressive. A check that only returns 200 hides a broken database connection; one that runs a heavy query causes outages by itself.
  • No headroom. Running at 90 percent of capacity means one failure triggers a cascade as remaining servers become overloaded and fail too.

How this shows up in interviews and real projects

Almost every design question includes a line like "we put a load balancer in front of several stateless servers", and interviewers often follow up with "how do you handle sessions?" or "what if the load balancer fails?". In real projects, cloud providers offer managed load balancers, so your job is mostly choosing the algorithm, health check and failure behaviour. Keep the system design interview prep guide nearby for how these topics are asked.

Try it yourself

Assume your API receives 2,000 requests per second at peak and one server can handle about 400 requests per second (both numbers are assumptions). How many servers do you need with N+1 redundancy? Then draw a diagram that avoids a load balancer SPOF and list where session data would live.

Show a hint

2,000 / 400 = 5 servers for peak, plus 1 spare for redundancy. For the diagram use two load balancers with failover, and put sessions in a shared store.

Key takeaways

  • Vertical scaling is simple but capped; horizontal scaling needs stateless servers but improves capacity and availability.
  • Move session and user state out of app servers into shared stores or tokens so any server can handle any request.
  • Pick the balancing algorithm to match your traffic: round robin for uniform requests, least connections for uneven ones.
  • Health checks and N+1 headroom keep one failure from becoming an outage.
  • Hunt for single points of failure at every layer, including the load balancer and the database.

Test yourself

A trick from this lesson's ride: Scale up means a bigger box; scale out means more boxes. Four more tricks like it wait along the route, each followed by a question.

Ride this lesson

Frequently asked questions

What is the difference between vertical and horizontal scaling?

Vertical scaling makes one machine more powerful by adding CPU, memory or disk. Horizontal scaling adds more machines and spreads the load among them. Horizontal scaling offers a higher ceiling and better fault tolerance but requires the application to be stateless or to coordinate state.

What is a stateless service and why does it matter for scaling?

A stateless service does not keep user-specific data in its own memory between requests, so any instance can serve any request. This lets you add or remove instances freely behind a load balancer, and a crashed instance loses nothing important.

Which load balancing algorithm should I use?

Round robin is a fine default when servers and requests are similar. Use least connections when request durations vary a lot, weighted variants when servers differ in capacity, and hashing when you want the same client or key to reach the same server, for example for cache locality.