Course contents

Load balancing: L4 vs L7

Load balancing for system design interviews: L4 vs L7 load balancers, round robin, least connections, consistent hashing and power of two choices, health checks, sticky sessions, connection draining, global DNS and anycast balancing, client-side gRPC balancing and keeping the load balancer from being a single point of failure.

FreeMust know for interviewsLesson15 min readBeginner → SeniorReviewed Oct 2026
On this page
  1. Why load balancers exist
  2. L4 vs L7: what the balancer can see
  3. Algorithms: how the next server is picked
  4. Health checks: active vs passive
  5. Sticky sessions, and why to avoid them
  6. Connection draining
  7. Global vs regional load balancing
  8. Client-side load balancing
  9. The load balancer as a single point of failure
  10. Practise: pick the layer
  11. How it shows up in design problems
  12. Interview follow-ups
  13. How to say it in the interview

⚖️ What is a load balancer?

A load balancer is the component that takes incoming traffic for one address and spreads it across a pool of servers doing the same job, skipping any that are unhealthy. If api.example.com runs on 12 identical instances, the load balancer is why clients see one name and the work is shared twelve ways. Picture the host at a busy restaurant: guests don't pick a waiter, the host seats each party where there's room, and stops seating anyone in a section whose waiter went home sick.

For this lesson, we'll cover the L4 vs L7 split, algorithms, health checks, sticky sessions, draining, global and client-side balancing, and how the balancer avoids being the weak link. Interviewers ask about it constantly because "add a load balancer" is the first scaling move in nearly every design, and the follow-up ("which kind, which algorithm, what if it dies?") shows who has run one.

Below the line (out of scope)

  • What a reverse proxy does beyond balancing (buffering, X-Forwarded-For, 502 vs 504): Proxy vs reverse proxy
  • Picking a region by name: Domain Name System (DNS)
  • The hashing ring itself: Consistent hashing
  • How clients find instances: Service discovery

The 30-second answer

A load balancer gives a pool of servers one address, spreads traffic across them and routes around failures. An L4 balancer works on TCP or UDP connections: it never reads the payload, so it's very fast and protocol-agnostic, but every request on a connection goes to the same backend. An L7 balancer terminates TLS and reads HTTP, so it can route by path or header, retry, and balance each request separately, at more CPU per request. I start with round robin for identical stateless servers, use least connections when request times vary, and consistent hashing when a key should land on the same node. Health checks pull bad servers out, draining lets deploys finish in-flight work, and the balancer itself runs as a redundant tier, never one box.

What you’ll learn

  • Explain L4 vs L7 load balancing and pick the right layer for HTTP, gRPC, WebSockets and UDP
  • Choose an algorithm (round robin, least connections, consistent hashing, power of two choices) to match the traffic
  • Tune active and passive health checks and work out how long a dead server keeps getting traffic
  • Replace sticky sessions with stateless servers, and drain connections safely during deploys
  • Layer global (DNS, anycast) and regional balancing, and make the balancer tier itself redundant

01Why load balancers exist

Four jobs, each building on the last:

  1. Scale out. Ten stateless servers behind a balancer handle roughly ten times the traffic of one.
  2. Survive failures. Health checks pull a dead instance, so a crash is a capacity dip, not an outage.
  3. Change without downtime. Deploys, canaries and autoscaling add and remove pool members behind a stable address.
  4. One front door. Clients know one name; the fleet behind it can change every minute.

02L4 vs L7: what the balancer can see

The layer number tells you how far into the packet the balancer looks, and that decides everything else. Here's the difference side by side.

Fig. 1

What an L4 and an L7 load balancer can see

What an L4 and an L7 load balancer can seeTwo panels. On the left, an L4 load balancer reads only the IP and TCP or UDP headers; the payload is opaque, so it hashes the connection and keeps every packet of that connection on one backend. On the right, an L7 load balancer terminates TLS and reads the HTTP request, so it can send paths starting with /api to the API pool and /static to the static pool, deciding per request.L4: sees a connectionL7: sees a request5-tuple onlyTLS decrypted/api/*/static/*IPTCP / UDPpayload: opaque bytesL4 load balancerhash(src, dst, ports, proto)server 1server 2server 3IPTCPGET /api/cart · Host · CookieL7 load balancerreads method, path, headersAPI pool3 instancesStatic pool2 instancesevery packet of a connection goes to the same servereach request is routed on its own
L4 balances connections; L7 balances requests. Reading the HTTP request is what makes path routing possible, and what costs the extra CPU.

An L4 balancer looks at addresses, ports and protocol (the 5-tuple), picks a backend when a connection starts, and forwards every packet of that connection to the same place. It doesn't decrypt anything. AWS's Network Load Balancer is the classic managed example; AWS documents it as working at layer 4 and handling millions of requests per second. An L7 balancer finishes the TCP and TLS handshakes itself, parses each HTTP request, and opens its own connections to backends, which makes it a reverse proxy.

L4 (transport) L7 (application)
Sees IPs, ports, TCP/UDP Method, path, host, headers, cookies
Decides Once per connection Per request (or per HTTP/2 stream)
TLS Passes it through (or terminates, in some products) Terminates it
Protocols Anything over TCP or UDP HTTP/1.1, HTTP/2, gRPC, WebSocket upgrade
Can do Very high throughput, low added latency, keep the client IP Path and header routing, retries, rewrites, auth hooks, per-request metrics
Costs No content-aware routing; long connections pin load More CPU per request; another TLS hop to manage
Typical product AWS NLB, Maglev, IPVS AWS ALB, nginx, Envoy, HAProxy in HTTP mode

The gRPC team's own guidance puts the trade-off neatly: pick L4 when latency or proxy cost matters most, and L7 when load varies a lot between connections, because only L7 can spread the streams of one HTTP/2 connection across several backends.

03Algorithms: how the next server is picked

There's no best algorithm, only the one that matches your traffic. This table is what I'd want a candidate to be able to reproduce.

Algorithm How it picks Fits when Watch out for
Round robin Next server in turn Identical servers, short, similar requests Ignores how busy each server is
Weighted round robin In proportion to weights Mixed instance sizes, sending 5% to a canary Weights are static guesses
Least connections Fewest active connections Request times vary, long-lived connections (WebSockets) A fresh server gets stampeded; use slow start
Least response time Lowest recent latency (and connections) Backends with uneven performance A server failing fast looks fast; combine with error checks
IP hash Hash of the client IP Cheap affinity without cookies A whole office behind one NAT lands on one server
Consistent hashing Hash a key (user ID, cache key) onto a ring Caches and per-key state; adding a node moves only about 1/N of keys A hot key is still hot; see Consistent hashing
Power of two choices Pick two servers at random, use the less loaded Many balancers, each with a partial or stale view Needs some load signal per server

The last one shows depth. If 20 balancer instances run "least connections" on stale counts, they all spot the same idle server and pile onto it. Sampling two at random and taking the better one keeps most of the benefit while making that herd very unlikely.

All of these are one line in nginx. The upstream docs list least_conn, ip_hash (which hashes the first three octets of an IPv4 address), hash $key consistent (ketama consistent hashing) and random two least_conn (power of two choices). Round robin is the default.

upstream orders_api {
    least_conn;                                   # or: random two least_conn;
    server 10.0.1.11:8080 weight=2;               # bigger box, twice the share
    server 10.0.1.12:8080 max_fails=3 fail_timeout=10s;
    server 10.0.1.13:8080 backup;                 # only used if the others are down
}

# For a cache tier, pin each key to a node instead:
# upstream cache_tier { hash $arg_key consistent; server ...; }

04Health checks: active vs passive

A balancer is only as good as its idea of which servers are alive. There are two ways to know.

  • Active checks: the balancer probes each target on a schedule (TCP connect, or GET /health expecting a 200) and flips its state after a run of failures or successes.
  • Passive checks: the balancer watches real traffic and ejects a server that keeps failing it. nginx's max_fails and fail_timeout work this way (defaults: 1 failure within 10 seconds marks the server unavailable for 10 seconds), and Envoy calls it outlier detection.

Use both: active checks catch a server before users hit it, passive checks react within a few requests. Make the health endpoint check what the instance needs to serve (can it reach its database?), but don't make it fail when a shared dependency is down, or every instance goes unhealthy at once and the balancer has nothing left to route to.

Fig. 2

Taken out by health checks vs drained on purpose

Taken out by health checks vs drained on purposeTwo timelines for one backend. Top: with Application Load Balancer defaults, checks run every 30 seconds, two consecutive failures mark the target unhealthy and it stops getting traffic; five consecutive passes are needed to bring it back. Bottom: for a deploy, the target is deregistered, stops receiving new requests, drains in-flight ones for up to 300 seconds by default, and is then safe to stop.Healthyprobe GET /healthevery 30 s1Check failstimeout 5 sfailure 1 of 22Fails againfailure 2 of 2:marked unhealthy3Out of rotationneeds 5 passesin a row to return4In rotationserving normally1Deregisterno new requestsare sent to it2Drainingin-flight requests finish,up to 300 s by default3Stoppedsafe to terminateor redeploy4Unplanned: active health checks (AWS ALB defaults)worst case to notice: about a minute of requests still reaching a dead targetPlanned: connection draining (deregistration delay)drain longer than your slowest normal request; long-lived sockets need a reconnect plan
Thresholds trade detection speed for stability. Unplanned removal waits for consecutive failures; planned removal stops new work first and lets old work finish.
Performance angle

Do the detection maths with real defaults. An AWS Application Load Balancer checks every 30 seconds with a 5-second timeout, marks a target unhealthy after 2 consecutive failures and healthy again after 5 consecutive passes. So a target that dies just after a good check keeps getting traffic for about 65 seconds (two intervals plus a timeout), and a recovered one waits about 150 seconds to return. If that's too slow, shorten the interval or lean on passive checks, which react on the first failed requests.

05Sticky sessions, and why to avoid them

Sticky sessions pin a user to one backend, usually with a cookie the balancer sets, to rescue apps that keep session state in memory. I treat them as a smell:

  • Uneven load. A few heavy users stuck to one box, and you can't move them.
  • Failures lose state. When that server dies, its users lose their sessions anyway.
  • Scaling doesn't help existing users. New instances only get new sessions.
  • Deploys drag. Draining waits on users who are pinned there.

The better default is stateless servers with sessions in a shared store such as Redis, or in a signed token. Affinity is fine as a performance hint (a user's data usually warm in one node's cache), not as a correctness requirement. WebSockets are sticky by nature, so plan for them to drop and reconnect.

06Connection draining

Removing a server on purpose shouldn't break its requests. Draining (AWS calls it deregistration delay) stops new requests to the target while in-flight ones finish; AWS waits 300 seconds by default. Set it a bit longer than your slowest normal request. For WebSockets and streams, drain for a while, then close them so clients reconnect elsewhere with backoff.

07Global vs regional load balancing

Everything so far happens inside one region. In front of that sits a second, coarser decision: which region should this user reach? Here's the full stack.

Fig. 3

Global, then regional: the load balancing stack

Global, then regional: the load balancing stackUsers worldwide are first sent to a healthy, nearby region by DNS or anycast. Inside each region, routers spread flows across several L4 load balancer machines using ECMP, the L4 tier hands connections to a pool of L7 proxies, and the proxies route requests to service instances. No layer is a single machine.Region ARegion BEU userUS userUsers worldwideGlobal: DNS or anycastpick a healthy region near the user1L4 tier: several LB machinesrouters spread flows with ECMP2L7 tier: proxy poolTLS, path routing, retries3Service instances4L4 tier: several LB machinesrouters spread flows with ECMPL7 tier: proxy poolTLS, path routing, retriesService instancesEvery tier is a group of machines, so losing one box is a capacity dip, not an outage.
Two decisions: which region, then which server. DNS or anycast makes the first, the regional L4 and L7 tiers make the second.
Global method How it works Good at Catch
DNS-based (GSLB) The DNS answer depends on the user's location, measured latency or region health Works with any protocol; simple; per-region weights Resolvers and clients cache answers until the TTL expires, so failover is as slow as caching allows; see DNS
Anycast Many sites announce the same IP over BGP, and internet routing delivers each user to a nearby one Fast failover (withdraw the route), absorbs DDoS across sites Routing can shift mid-connection; you need your own network presence; see IP addresses

Large services combine them, then balance inside the region. Multi-region and active-active covers the data side.

08Client-side load balancing

Inside a microservice system, there's often no balancer box at all. Each client gets the instance list from service discovery and picks one per call: one hop fewer and no central bottleneck, at the cost of balancing logic in every client (or a sidecar such as Envoy).

gRPC is where this matters most. A gRPC client multiplexes all its calls over one long-lived HTTP/2 connection, so behind an L4 balancer every call from that client lands on one backend, and adding servers doesn't spread existing load. gRPC's default pick_first policy has the same effect. Switching to round_robin makes the client connect to every address the resolver returns and rotate per call:

ManagedChannel channel = ManagedChannelBuilder
        .forTarget("dns:///orders.internal:8443")   // resolver returns every instance
        .defaultLoadBalancingPolicy("round_robin")  // default is pick_first
        .build();

The alternatives are an HTTP/2-aware L7 balancer or a service mesh. The gRPC team recommends client-side balancing for trusted, high-traffic clients and a proxy when you need a trust boundary.

09The load balancer as a single point of failure

If every request goes through one balancer, you've moved the single point of failure, not removed it. Two patterns fix that:

  • Active-passive pair: two balancers share a floating virtual IP (VRRP, for example via keepalived); the standby takes the IP when the active one stops sending heartbeats. Simple, but half the hardware idles and the pair's capacity is one box.
  • Active-active tier: many balancer machines, all serving. Upstream routers spread flows across them with ECMP, or anycast spreads them across sites. Losing one costs 1/N of capacity.

Managed cloud balancers do this for you: AWS documents that enabling an Availability Zone creates a load balancer node in that zone, and the balancer's DNS name resolves to the nodes. Your job is to enable at least two zones and keep enough headroom in each.

In the real world

Google's Maglev, described in an NSDI 2016 paper, is the active-active pattern at full scale. It's a software L4 balancer on ordinary Linux servers that had been serving Google's traffic since 2008. Routers spread packets across Maglev machines with ECMP; each machine picks a backend, wraps the packet in GRE and sends it on, and the backend replies directly to the user (direct server return), because responses are much larger than requests. The paper notes that hardware balancers are usually deployed as active-passive pairs, while Maglev runs every machine active for N+1 redundancy. Its consistent hashing plus connection tracking means that when a Maglev machine is added or lost, packets of existing connections still reach the same backend. A single machine can saturate a 10 Gbps link with small packets. Cloudflare's Unimog takes the idea further and makes every edge server part of the L4 balancer.

10Practise: pick the layer

Try it yourself first

Before reading on, choose L4 or L7 (or "either") for each, and say why in one sentence: WebSockets for a chat app, gRPC between internal services, UDP traffic for a multiplayer game, and path-based routing so /api and /images go to different services.

Here's how I'd answer out loud.

Traffic Layer Why
WebSockets Either; L7 if you route or authenticate on the upgrade It starts as an HTTP request, so L7 can route by path and check auth; after that it's one long connection, so balancing happens once either way. Use least connections, raise idle timeouts and drain carefully
gRPC L7 or client-side One HTTP/2 connection carries every call; L4 pins them all to one backend. L7 (or a round_robin client) spreads individual calls
Game UDP L4 There's no HTTP to read, and an L7 proxy would only add latency. Hash on the 5-tuple (or a session ID in the packet) so a player's packets stay on one server
Path-based routing L7 only The path lives inside the HTTP request, which an L4 balancer never reads

11How it shows up in design problems

  • Design a URL shortener: stateless app servers behind an L7 balancer with round robin; the redirect path is short and uniform.
  • Design WhatsApp: millions of long-lived WebSocket connections, so least connections, draining plans and a mapping of which server holds which user's connection.
  • Design an LLM gateway: request cost varies by orders of magnitude, so least outstanding requests or power of two choices beats round robin.
  • Design a distributed cache: consistent hashing on the key, so a node change only moves a slice of the cache.
  • Design a rate limiter: hash the user or API key to one limiter node so its counter isn't split across many.

At this point, you should ask your interviewer whether servers hold any per-user state. That one answer decides between round robin, hashing and sticky sessions.

12Interview follow-ups

What if the interviewer asks why not use DNS round robin instead of a load balancer?

DNS can spread users across a few addresses, but it can't react quickly: answers are cached until the TTL expires, clients often stick to the first address, and DNS has no idea whether a server is overloaded. It's a good fit for picking regions or balancer nodes, and a poor fit for picking individual app servers.

What if a new instance joins and immediately gets flooded?

That's least connections treating an empty server as the best target, while its caches and JIT are still cold. Use slow start (ramp its weight up over a minute or two) or power of two choices, and make readiness checks pass only once it's warmed up.

What if your health checks all pass but users are seeing errors?

The check is too shallow, for example it returns 200 whenever the process is up. Add passive checks that eject servers returning 5xx on real traffic, and make the active check exercise what a request needs. Then check whether the errors come from a shared dependency, which no amount of rerouting will fix.

What if the whole region's load balancer tier fails?

Global balancing takes over: DNS health checks or an anycast route withdrawal send users to another region. How fast depends on DNS TTLs or BGP convergence, and the other region needs headroom to absorb the traffic. Without that headroom, failing over just moves the outage.

13How to say it in the interview

When I ask about load balancing, I'm listening for four things: that you pick a layer for a reason, that your algorithm matches the traffic, that you know how unhealthy servers get removed, and that the balancer itself isn't a single box.

In the interview

"Users first hit DNS or anycast, which sends them to a healthy region nearby. Inside the region, an L4 tier spreads TCP connections across a pool of L7 proxies, and the L7 layer terminates TLS and routes by path: /api to the API service, /media to the media service. The app servers are stateless, so I'd use round robin, or least connections for the endpoints with variable latency; sessions live in Redis, so no sticky sessions. Health checks probe /health every few seconds and need two failures before a server is pulled, with passive ejection on 5xx as a backstop. Deploys deregister instances and drain for about 30 seconds so in-flight requests finish. No tier is one machine: the L4 tier is active-active behind ECMP across at least two zones. For internal gRPC calls I'd balance per call on the client, because an L4 balancer would pin each client's HTTP/2 connection to one backend."

On the whiteboard

  • Why: scale out · route around failures · zero-downtime deploys · one address
  • L4 = 5-tuple, per connection, fast, any protocol · L7 = HTTP, per request, TLS, path routing
  • Algos: RR · weighted · least conn · least time · IP hash · consistent hash · P2C
  • Health: active probes + passive ejection · N failures out, M passes back
  • Avoid sticky sessions → stateless + shared session store
  • Drain on deploy (AWS default 300 s)
  • Global: DNS (TTL) / anycast (BGP) → regional L4 → L7 → service
  • gRPC: L7 or client-side round_robin, not L4
  • LB redundancy: active-active + ECMP, ≥ 2 zones (Maglev N+1)
Further reading (6)