Load Balancing and Traffic Management Questions
Distributing requests across capacity: load-balancing algorithms (round-robin, least-connections, consistent hashing), L4 versus L7 balancing, health checks, and traffic shaping. Covers sticky sessions, canary and blue-green routing, rate limiting, and graceful draining. The traffic-distribution layer that keeps a scaled system balanced and available.
You need to roll out an update to a service behind an L7 load balancer that still uses sticky sessions. Compare blue-green, canary, and rolling-update approaches: for each, explain how you would drain connections, how you would migrate or preserve session state, and how you would validate success before committing.
Sample Answer
Direct Answer
All three deployment strategies need the same underlying primitive when sticky sessions are involved: keep serving already-affinitized sessions from their old destination while steering new sessions to the new version, and validate with real traffic before fully committing. What differs is blast radius and rollback speed. Canary gives the smallest blast radius and fastest safe rollback since only a slice of traffic ever touches the new version. Blue-green gives the fastest full rollback, a single traffic flip back to the untouched old fleet. Rolling update is the middle ground: it uses less infrastructure than blue-green but exposes both versions to production traffic for the longest window.
Comparing the Three Approaches
| Approach | Connection draining | Session state handling | Validation before commit | Rollback |
|---|---|---|---|---|
| Blue-green | Old (blue) fleet stays fully up and serving until cutover; drain blue only after traffic is flipped | Best served by an externalized session store so sessions survive the fleet swap cleanly; if sessions are cookie-pinned to instances, the cutover needs a session-mirroring step | Gradual traffic shift (e.g., 10% then 100%) while watching error rate, latency, and session-continuity checks before fully committing | Flip the router back to blue; near-instant since blue was never torn down |
| Canary | New (canary) instances take only new sessions; canary is scaled down and drained gracefully if promoted or rejected | Same principle: externalized state lets a session move between canary and baseline transparently; if using cookie affinity, route by cookie so a canaried session stays on a canary instance able to handle it | Small, tight-SLO evaluation window on a small slice of real traffic, with automated gates (error rate, latency, business metrics) before expanding | Set canary traffic weight to zero and terminate; blast radius was already small, so rollback impact is minimal |
| Rolling update | Each instance is marked out of rotation, allowed to finish in-flight work (or hand off state), then replaced, in small batches | Requires session state to be externalized or explicitly handed off before an instance is replaced, since there's no single "old fleet" to fall back to | Monitor per-batch metrics with a pause-and-check gate between batches, rather than one global before/after comparison | Halt the rollout and redeploy the previous version to the already-replaced instances; slower than blue-green because it's also incremental |
Worked Example
If a canary receives 5% of instance capacity and that version has a latent bug, the fraction of live traffic that can be affected during the validation window is bounded above by that same 5%, by construction, since the routing weight is what determines exposure. A rolling update, by contrast, exposes a growing share of the fleet as each batch completes; if you roll in 10 batches of 10% each and something is only caught at the fourth batch, roughly 40% of the fleet has already been exposed by the time you halt, an order of magnitude more exposure than the canary case for the same underlying bug. This is the concrete reason canary is preferred for higher-risk changes even though it takes more total upfront tooling to run the automated gates.
Trade-offs and Pitfalls
- Blue-green doubles infrastructure cost for the duration of the cutover window, since two full fleets run simultaneously; this is the price of the fastest possible full rollback.
- Rolling update's extended window with two versions live simultaneously creates a real risk of version-skew bugs: if a sticky client reconnects mid-rollout and lands on a different version than its previous request, and the two versions disagree on session schema or API contract, that's a bug class that blue-green and canary largely avoid by keeping version boundaries sharper.
- Canary's small sample size is a double-edged sword: it bounds blast radius but also means a rare bug (one that only manifests on 1 in 1000 requests) may not surface at all before the canary is judged "clean" and promoted.
- Whichever strategy is used, connection draining timeouts need to be sized for the actual protocol involved; a WebSocket-heavy service needs a much longer drain window than a typical request-response API, and a drain window that's too short converts a graceful strategy into a de facto hard cut for anything still in flight.
Describe how you would coordinate load balancer connection draining with autoscaling group scale-in so instances finish in-flight work before termination. Cover cooldowns, warm-up periods, and how the load balancer's health-check readiness state should gate when a new instance starts receiving traffic.
Sample Answer
Direct answer
The load balancer must gate two moments in an instance's life: it should not receive traffic until its readiness check passes (not just a process-alive check), and on scale-in it must stop receiving new connections while letting in-flight requests finish, deregistration with connection draining, before the instance is terminated. Both are the LB's job, independent of whatever policy decided to scale in the first place.
Structured elaboration
Scale-out: readiness gates traffic, not liveness. A liveness check ("is the process up") and a readiness check ("is the app actually able to serve correctly") are different questions. The LB should register the instance in rotation, or the orchestrator should mark it Ready, only after readiness passes: connection pools opened, local caches warmed enough to avoid a cold-cache latency cliff, and any required config pulled. A liveness-only gate puts unwarmed instances into rotation and produces a latency or error spike on every scale-out event.
Scale-in: draining before termination, in order:
- Instance is marked for termination (by the autoscaler or an operator).
- The LB immediately stops sending new connections to it (deregister from rotation) but does not kill existing connections.
- A deregistration delay (connection draining window) gives in-flight requests time to complete normally.
- Only after the drain window elapses, or all in-flight requests finish, whichever is defined as the policy, does the instance actually terminate.
Sizing the drain window. It must exceed the longest realistic in-flight request duration for the service, with margin, or draining will forcibly cut live requests, defeating the point.
Health-check readiness during warm-up. The initial health-check grace period (the time before a failed check counts against a brand-new instance) must exceed real startup time, so a slow-booting instance is not falsely marked unhealthy and cycled before it ever gets a chance to serve.
Cooldowns, and where autoscaling policy stops and LB mechanics start. Which metric and threshold trigger a scale event is an autoscaling-policy decision outside this question's scope, but the cooldown itself belongs here since the question names it directly: a cooldown is the minimum time an autoscaler must wait after one scaling action before it is allowed to trigger another, and it exists because the fleet's aggregate metrics take time to reflect the previous action, an autoscaler that reacts to a still-stale signal will overreact, stacking a second scale-out on top of one still warming up, or yanking capacity out from under a scale-in that hasn't finished draining. The cooldown has to be sized against the LB mechanics above, not chosen independently of them: it should exceed both the drain window (a scale-in's capacity isn't actually gone until draining finishes) and the health-check grace period (a scale-out's capacity isn't actually serving, and won't show up in the fleet's metrics, until warm-up and readiness finish), see the worked example below for a concrete number. Whatever triggers a scale-out or scale-in event, the LB's readiness gate and drain sequence are what make that event safe for in-flight traffic; the cooldown is what keeps the autoscaler from firing faster than those mechanics can complete.
Worked example
Say p99 request duration for this service, measured over the last observation period, is 8 seconds, and cold-start warm-up (open DB pool + populate local cache) takes 15 seconds.
Deregistration delay, with margin over the slowest realistic request:
drain window=8s×1.25≈10sHealth-check grace period, with margin over startup time:
grace period=15s+10s (buffer)=25s, rounded up to 30sSo: a new instance is excluded from the health check's failure count for its first 30 seconds while it warms up, and a terminating instance gets up to 10 seconds to finish in-flight requests after being pulled from rotation, before the process is killed.
Cooldown, sized to exceed both of those numbers so the autoscaler never acts on a stale signal:
cooldown=max(drain window,grace period)×1.5≈30s×1.5=45sSo the autoscaler waits at least 45 seconds after any scale-out or scale-in action before it will consider triggering another one, comfortably longer than the 30 second grace period a new instance needs before its readiness, and therefore its true added capacity, is even visible in the fleet's metrics.
Trade-offs and pitfalls
- Too-short drain windows silently drop real user requests mid-response; this is one of the most common causes of a small, hard-to-reproduce error-rate blip during routine scale-in.
- Too-long drain windows slow down scale-in, which matters when scale-in is reacting to a cost or capacity signal; there's a real tension between "never cut a request" and "shed capacity promptly."
- A liveness-only readiness gate causes a recurring latency spike on every scale-out, because unwarmed instances start serving immediately; teams often discover this only when scale-out frequency increases (e.g. spiky traffic) and the spikes become visible in aggregate latency graphs.
- Grace periods that are too short flap healthy-but-slow-booting instances, causing the autoscaler and LB to fight each other: instance boots, fails health check before finishing warm-up, gets cycled, repeats.
- Drain and readiness are load-balancer-owned; they are not a substitute for correct autoscaling trigger design. A perfectly tuned drain window cannot fix an autoscaler that scales in too aggressively in the first place, that is a separate, upstream problem.
Explain consistent hashing in the context of traffic distribution and distributed caches. Describe the hash ring, why virtual nodes are used, and how it minimizes remapping compared to plain modulo hashing when a node is added or removed. Walk through a small example.
Sample Answer
Direct answer
Consistent hashing maps both servers and keys onto the same hash space, conceptually a ring, so that a key belongs to the first server found going clockwise from the key's hash position. Unlike plain modulo hashing, where server = hash(key) mod n, changing the number of servers under consistent hashing only moves the keys sitting in the arc next to the server that was added or removed; every other key stays put. Virtual nodes, where each physical server is hashed to many points on the ring instead of one, are used to smooth out the otherwise lumpy arcs that a small number of real servers would create.
Structured elaboration
- The hash ring. Hash both server identifiers and keys into the same range, for example 0 to 255, and think of that range as a circle. To find the owner of a key, hash the key and walk clockwise until you hit the first server position.
- Why it minimizes remapping. When a server is added or removed, only the keys between that server's position and its predecessor's position need to move. Everything else still maps to the same server it did before, because no other server's position on the ring changed.
- Why virtual nodes. With only a handful of real servers placed at a few random points, the arcs between them can be very unequal by chance, so one server ends up owning a disproportionate share of keys. Giving each physical server many virtual positions (commonly 100 to a few hundred) averages this out and also means a removed server's keys spread across many surviving servers instead of dumping them all on one neighbor.
How much remapping this actually saves, comparing consistent hashing to naive modulo hashing when going from n to n+1 servers:
With consistent hashing, the new server claims a share of the ring and only those keys move; the expected fraction of the keyspace that moves is:
fraction moved (consistent hashing)=n+11With naive modulo hashing, a key keeps its server only when hmodn=hmod(n+1). Since n and n+1 share no common factor, that equality holds for exactly n values out of every n(n+1) consecutive hash values, so the fraction of keys that keep their server is 1/(n+1), and the fraction that must move is:
fraction moved (naive modulo)=n+1nWorked example
Small ring walkthrough. Three servers A, B, C hash to positions 10, 50, 200 on a 0-255 ring. Key k1 hashes to 20: walking clockwise from 20, the first server position is B at 50, so k1 maps to B. Key k2 hashes to 180: the first server position clockwise is C at 200, so k2 maps to C. Key k3 hashes to 230: there is no server position at or above 230, so the walk wraps around to the smallest position, A at 10, and k3 maps to A.
Now remove C. Only keys that previously mapped to C, meaning keys hashing into (50, 200], need to move. k2 (180) is in that range: walking clockwise from 180 with C gone, there is no server until the wrap to A at 10, so k2 now maps to A. k1 (20, maps to B) and k3 (230, already wrapping to A) are both unaffected, exactly as the fraction-moved formula predicts: only the keys in C's old arc move.
Numeric scale. With n=10 servers and K=1000000 keys, adding an 11th server:
keys moved (consistent hashing)=K×n+11=1000000×111≈90909 keys moved (naive modulo)=K×n+1n=1000000×1110≈909091Consistent hashing moves roughly 9.1% of the keyspace; naive modulo hashing moves roughly 90.9%, which for a cache means the equivalent of a near-total cold start on every capacity change.
Trade-offs & pitfalls
- Virtual node count is a real tuning knob: too few and load is lumpy across whichever real nodes happen to own the largest arcs; too many and every lookup and rebalance touches more ring entries and more memory.
- A perfectly balanced ring does not protect against a hot key: hashing spreads keys evenly, not access frequency, so one very popular key can still overload the single node that owns it.
- Weight servers by capacity through proportional virtual node counts (a server with twice the capacity gets twice the virtual nodes), rather than assuming uniform hashing will "even out" heterogeneous hardware on its own.
Why are load balancers used in distributed systems? Describe at least four distinct problems they solve, and how they contribute to horizontal scaling, availability, and fault isolation. Where would you typically place load balancers in a three-tier architecture (edge, internal service, service-to-service)?
Sample Answer
Direct answer
Load balancers exist to decouple how much traffic arrives from which single machine has to answer it. They solve at least four distinct problems: spreading load across available capacity so no single server bottlenecks, removing single points of failure through health-based failover, enabling safe rollouts such as canary (releasing a new version to a small slice of traffic first) and blue-green deployment (running old and new versions side by side, then cutting all traffic over at once) with graceful draining, and centralizing cross-cutting concerns like TLS termination and rate limiting. In a typical three-tier architecture, load balancers show up at the edge (public-facing traffic), between internal services (service to service), and inside a service mesh (sidecar to sidecar).
Structured elaboration
Named problems load balancers solve:
- Single-server bottleneck / horizontal scaling. Spreading requests across many instances lets the system add capacity by adding instances instead of a bigger single machine.
- Availability and automatic failover. Health checks remove failing instances from rotation so one bad node does not take down the service.
- Fault isolation and safe rollout. Canary and blue-green routing, plus connection draining during deploys, contain a bad release to a small slice of traffic instead of every user.
- TLS termination and centralized security policy. Certificates and security rules (WAF, Web Application Firewall: a layer that inspects and blocks malicious HTTP traffic before it reaches a backend, and rate limiting) live in one place instead of being duplicated on every backend.
- Rate limiting and overload protection. The balancer can shed or throttle excess traffic before it reaches backend capacity, protecting downstream services.
Placement in a three-tier architecture:
graph LR
Client[Client] --> EdgeLB["Edge LB (L7, TLS termination)"]
EdgeLB --> GwA[Gateway instance A]
EdgeLB --> GwB[Gateway instance B]
GwA --> InternalLB[Internal service LB]
GwB --> InternalLB
InternalLB --> SvcA[Service instance A]
InternalLB --> SvcB[Service instance B]
SvcA --> MeshLB["Sidecar LB (service-to-service)"]
SvcB --> MeshLB
MeshLB --> Downstream[Downstream service]
The edge tier handles public traffic and usually terminates TLS. The internal service tier balances across replicas of a given service, often behind a service discovery layer. The service-to-service tier (frequently a sidecar, a small proxy process deployed alongside each service instance, in a mesh) balances outbound calls one internal service makes to another, applying the same health-check and retry logic without a separate hop through a centralized balancer.
A fast way to spot gaps in an unfamiliar system's traffic layer: check whether all three tiers actually exist (a single edge LB fanning straight out to every backend is a common shortcut that skips the internal tier), whether health checks are active at each tier, and whether TLS termination and rate limiting are centralized or scattered across services.
Worked example
Three backend instances, each rated for 1,000 requests per second, pooled behind a load balancer: combined capacity is 3×1000=3000 requests per second. If health checks detect one instance failing and pull it out of rotation, capacity becomes 2×1000=2000 requests per second, so the system keeps serving at 2000/3000≈66.7% of its original capacity instead of dropping to zero, which is what a single unpooled instance would do on failure.
Trade-offs & pitfalls
- The load balancer itself becomes critical infrastructure. A single-instance balancer just moves the single point of failure up one layer; it needs its own high availability (active-active pair, DNS or anycast failover).
- Every additional tier (edge, internal, mesh) adds a proxy hop's worth of processing and another thing to operate; do not add a tier a system does not need.
- A misconfigured health check at any tier can silently remove that tier's benefit (failover, safe rollout) even though the balancer process is technically running.
Design a Layer 4 load balancing architecture that supports hundreds of millions of concurrent connections with minimal disruption on failure. Cover state-distribution approaches (stateful proxies with replication, stateless consistent hashing, flow-director hardware), session affinity, and how connections rehome after an instance fails. Work through a rough capacity model: given per-instance connection and bandwidth limits and a target RPS, how many load balancer instances would you provision, and how much headroom would you reserve for autoscaling lag?
Sample Answer
Direct answer
At hundreds-of-millions-of-flows scale, L4 balancing splits into two layers: a coarse steering layer (BGP, Border Gateway Protocol, the protocol routers use to exchange and agree on internet routes, anycast, plus ECMP, Equal-Cost Multi-Path routing: spreading traffic for one destination across several equally-good network paths) that spreads flows across a fleet of L4 proxy/NAT instances, and a per-flow layer where you decide how much connection state to keep and replicate. The three real options are stateful proxies with peer replication, stateless consistent hashing, and NIC/SmartNIC flow-director hardware, and they trade replication cost against how gracefully a flow survives an instance failure. Instance count is not a single RPS division: you size against whichever resource (concurrent connections, new-connection rate, or bandwidth) saturates first, then pad for the time it takes autoscaling to react.
Architecture
flowchart LR
Client[Client] -->|Anycast IP| Edge[BGP Anycast + ECMP]
Edge -->|hash 5-tuple| LB1[L4 LB Instance A]
Edge -->|hash 5-tuple| LB2[L4 LB Instance B]
LB1 <-->|flow-state sync| LB2
LB1 --> Pool[Backend Pool]
LB2 --> Pool
HC[Health Check Controller] -.probe.-> LB1
HC -.probe.-> LB2
HC -.withdraw route.-> Edge
Clients hit one anycast VIP; the network fabric's ECMP hash spreads flows across the LB instance fleet by 5-tuple (the five fields, source IP, destination IP, source port, destination port, and protocol, that together identify one network flow), so each instance only ever sees a shard of total flows. A separate health-check control plane probes each instance and withdraws its route (or its ECMP weight) on failure, which is what actually removes it from the data path, not the instance's own shutdown.
State-distribution approaches
| Approach | How it works | Failure behavior | Cost |
|---|---|---|---|
| Stateful proxy + replication | Each instance tracks full TCP state (seq/ack windows, NAT mapping) and replicates a minimal delta to 1+ peers, usually same-rack over RDMA (Remote Direct Memory Access: one machine reading or writing another machine's memory directly over the network, bypassing its CPU) or shared memory | Peer can take over an in-flight flow using replicated state; connection survives | Replication bandwidth and complexity scale with connection churn rate, not just count |
| Stateless consistent hashing | Hash 5-tuple to a backend index; the LB instance itself holds no durable per-flow state beyond a NAT table it can rebuild | On instance loss, consistent hashing remaps only the flows that were pinned to it; those connections reset and rely on client/application reconnect | Cheapest to run and scale; pushes the cost onto the application needing idempotent reconnect logic |
| Hardware flow-director (SmartNIC/NIC TCAM, Ternary Content-Addressable Memory: specialized NIC hardware that can match a packet against thousands of rules in a single lookup) | NIC pins a flow to a physical queue/core via a TCAM rule, offloading connection tracking from the CPU | Control plane rewrites TCAM entries to redirect flows on failure; fast, but bounded by TCAM table size | Lowest per-flow CPU cost at line rate; adds a hardware dependency and a TCAM-capacity ceiling |
In practice these combine, though not as equal options: stateless consistent hashing is the default most teams reach for first, since it needs no special hardware and simply pushes cost onto clients that are already expected to be idempotent; stateful replication is reserved for flows that explicitly need zero-loss continuity (e.g. long-lived streaming sessions); and SmartNIC/TCAM hardware offload is the rare, specialized end of the spectrum, worth reaching for only once software-only steering is demonstrably the bottleneck, not a default starting point.
Session affinity and rehoming
- Affinity granularity: 5-tuple hashing gives per-connection pinning; client-IP hashing gives session-level stickiness across ephemeral ports for NAT'd clients at the cost of coarser load spreading.
- Graceful drain: mark an instance DRAINING, stop admitting new flows, let existing ones finish within a bounded timeout, then remove it. This is the common case (deploys, scale-down) and causes no client-visible disruption if the timeout is respected.
- Abrupt failure: for stateful+replicated flows, the peer takes over using replicated metadata and rewrites NAT so the client sees the same source IP/port. For stateless flows, the client's TCP connection resets; consistent hashing bounds the blast radius (only that instance's shard remaps) but the application still needs idempotent retry/reconnect logic, since L4 alone cannot resume a dropped TCP stream transparently.
Health checking
Combine active probing (TCP SYN/connect probes at a fixed interval) with passive signals (retransmit rate, RST rate, per-flow error counters via eBPF, small, safe custom programs run inside the Linux kernel to observe or filter packets without a kernel rebuild) so a slow-degrading instance is caught before active probes alone would flag it, and require multiple consecutive failures before marking UNHEALTHY to avoid flapping on transient loss.
Worked capacity model
Provisioning is set by whichever of three resources saturates first: concurrent-connection ceiling, new-connection (accept) rate, or bandwidth. Take the following pinned per-instance limits and target load as the model inputs:
- Target: 300,000,000 concurrent connections, 3,000,000 new connections/sec at peak, 1.5 KB/s average sustained throughput per active flow.
- Per-instance limits (design assumptions for this model): 2,000,000 max concurrent connections, 50,000 max accepted connections/sec, 20 Gbps usable bandwidth after overhead.
Bandwidth is the binding constraint here (180 instances), not raw connection count or accept rate. This matters: an engineer who sizes only off "connections per instance" would under-provision by 30 instances and be bandwidth-saturated in production.
Now add headroom for autoscaling lag. If it takes L seconds from crossing a scale-out threshold to a new instance being healthy and in rotation (health-check interval, boot time, warm-up), and traffic can grow at a worst-case rate of g per second during a spike, the fleet needs to absorb that much growth before the new capacity lands. Using L=90 seconds and g=0.2%/s as pinned modeling inputs:
headroomNprovisioned=(1+g)L−1=(1.002)90−1≈0.197=⌈Nsteady×(1+headroom)⌉=⌈180×1.197⌉=216So provision 216 instances against a 180-instance steady-state requirement, roughly 36 instances (20%) of standing headroom to cover the autoscaler's reaction window.
Trade-offs and pitfalls
- Stateful replication buys zero-loss continuity but the replication channel itself becomes a new failure surface and a source of tail latency if it's not kept off the request-critical path.
- Pure stateless hashing is the cheapest to operate at this scale but silently pushes correctness work (idempotent retries, reconnect handling) onto every client and application team; it only works if that contract is actually honored end to end.
- SmartNIC flow-director gives the best CPU efficiency per flow but ties capacity planning to TCAM table size, not just CPU or memory, and adds a hardware refresh dependency.
- The most common sizing mistake is computing instance count from one resource (usually RPS or connection count) and ignoring the other two; always take the max across connection, rate, and bandwidth ceilings, and always separately budget headroom for autoscaler lag rather than folding it into a vague overall safety margin.
Unlock Full Question Bank
Get access to all Load Balancing and Traffic Management interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.