WierdX — Programming Reference All tutorials →
Developer reference · Practical tutorials · CS fundamentals
Systems & Infrastructure

Load Balancing Algorithms Explained: Round Robin, Least Connections, and Weighted Choices

A load balancer's job sounds simple: pick a backend for each request. The algorithm it uses decides whether that choice holds up under real traffic, or quietly overloads one server while three others sit idle.

Published July 6, 2026

Put a load balancer in front of five identical backend servers and the naive assumption is that any distribution algorithm works about the same. It doesn't. Request duration varies, connections stay open for different lengths of time, and servers rarely stay perfectly identical once one of them starts running a slow garbage collection cycle or a background job. The algorithm decides how fast the balancer reacts to that.

Round robin: simple, and simple to break

Round robin sends request 1 to server A, request 2 to server B, request 3 to server C, then cycles back to A. It requires no state beyond a counter, which is why it's the default in most load balancer configs. It works well when requests are roughly uniform in cost — a JSON API returning small payloads with similar processing time per request.

It breaks down when request cost varies. If server A happens to receive three slow report-generation requests in a row while B and C get fast lookups, round robin keeps sending A a fourth request anyway, because it has no idea A is busy. It only knows whose turn it is next.

Weighted round robin

Weighted round robin fixes the case where backends have different capacity, not different per-request cost. If server A has twice the CPU of server B, you assign it weight 2 so it receives two requests for every one B receives:

upstream backend {
    server 10.0.0.1 weight=2;
    server 10.0.0.2 weight=1;
    server 10.0.0.3 weight=1;
}

This is a static ratio decided at configuration time. It doesn't adapt to a server slowing down mid-day; it only encodes what you already know about relative hardware capacity.

Least connections

Least connections routes each new request to whichever backend currently has the fewest open connections. This is the first algorithm on this list that actually looks at live server state rather than just cycling through a list. It handles uneven request duration well: if server A is stuck processing five long-running report requests, new short requests go to B and C instead, because A's open-connection count is higher.

The catch is that "fewest connections" isn't the same as "least loaded." A server can have few open connections but be pegged at 100% CPU on a single expensive query. Least connections is a good proxy for load, not a perfect one, which is why some balancers add response-time tracking on top of it (sometimes called "least response time" or "peak EWMA" balancing).

Consistent hashing for sticky routing

Sometimes you need the same client, or the same cache key, to consistently land on the same backend — for session affinity, or so a caching layer in front of each backend actually gets cache hits instead of a different backend missing every time. Hashing the client IP or a session cookie into a fixed set of buckets works until you add or remove a server, at which point a naive modulo hash reshuffles almost every mapping at once.

Consistent hashing solves exactly this problem: adding or removing one backend only remaps the requests that were assigned to that backend, leaving everyone else's routing untouched. It's the same technique used to route keys across shards in a distributed cache, applied here to route requests across backends.

Layer 4 vs Layer 7 load balancing

An L4 load balancer works at the TCP/UDP level: it looks at IP addresses and ports and forwards packets without understanding HTTP at all. It's fast and cheap but can't route based on URL path, headers, or cookies. An L7 load balancer terminates HTTP (or HTTPS) and can inspect the request — routing /api/* to one backend pool and /static/* to another, or routing based on a header value. Most production setups use L7 for this flexibility, accepting the extra CPU cost of parsing HTTP on every request.

Health checks decide who's even in the pool

None of these algorithms matter if the balancer keeps sending traffic to a backend that's already down. Health checks run independently of the routing algorithm: an active check polls a lightweight endpoint (often /healthz) every few seconds and pulls a backend out of rotation after a configurable number of consecutive failures. Passive health checking watches real request outcomes instead — if a backend returns a run of 5xx errors or times out repeatedly, it gets marked unhealthy without a separate polling request. Combining both catches failures faster: active checks find a backend that's completely unreachable, passive checks find one that's technically up but returning garbage.

Picking one

For uniform, short-lived requests, round robin is fine and adds almost no overhead. For workloads with variable request cost — mixed read/write traffic, some slow endpoints and some fast ones — least connections adapts better because it reacts to actual server state instead of a fixed rotation. Reach for consistent hashing specifically when you need session affinity or cache locality, not as a general-purpose default; it solves a narrower problem than the other two. Whatever you pick, pair it with health checks, or the smartest algorithm in the world will still send traffic to a dead server.