Reading — step 1 of 5
Read
Round-Robin Load Balancing
A load balancer is the front door to a fleet: one address on the outside, N interchangeable backends on the inside. Every arriving request forces the same small decision — which backend gets this one? That selection function is the heart of the balancer; everything else in this course (health checks, stickiness, draining) is machinery wrapped around it.
Round-robin is the baseline answer: hand out backends in a fixed cycle.
backends: [server1, server2, server3]
request 1 -> server1
request 2 -> server2
request 3 -> server3
request 4 -> server1 # wrap around
The whole implementation is a counter and a modulo:
It is the default for a reason: nginx's upstream block, HAProxy's balance roundrobin, and AWS ALB all round-robin unless you configure otherwise. When requests cost roughly the same, an even count is an even load — and the decision needs no feedback from the backends at all: O(1), one integer of state.
Where it breaks
Round-robin is blind by design, and each blindness becomes a later lesson:
- It ignores load. A 2-second
/reportcounts the same as a 2 ms/ping. A struggling backend keeps receiving its full share while it drowns. Least-connections fixes this. - It ignores health. A dead backend stays in the cycle and eats every Nth request. Health checks fix this.
- It ignores identity. The same client hops backends on every request, so server-local session state is lost. Sticky sessions fix this.
The traps
The empty pool. idx % len(backends) raises ZeroDivisionError when the pool is empty. A real balancer has to decide what "a request with zero backends" means (usually a 503); your implementation must handle it explicitly instead of crashing.
Stale index on pool change. If the pool shrinks from 5 backends to 3 while idx is 4, naive backends[idx] is an IndexError. Real balancers rebuild scheduler state on config reload; in this exercise, POOL resets the counter to 0 — the simplest correct policy.
Concurrency. Two threads that both read idx before either increments it pick the same backend. Production round-robin uses an atomic fetch-and-add (atomic.AddUint64 in Go, AtomicInteger.getAndIncrement in Java) — the counter is the whole critical section. Our grader is single-threaded, so a plain integer is fine here, but know why the real ones look different.
Your exercise
Implement the RoundRobin class behind the command loop in the starter: POOL <b1> <b2> ... replaces the backend list and resets the counter (prints OK), PICK prints the next backend, RESET zeroes the counter without touching the pool (prints OK), and PICK on an empty pool prints EMPTY.
Mistakes the grader will catch, straight from the tests:
- Advancing before returning. The first
PICKafterPOOL s1 s2 s3must prints1, nots2. - Not resetting the cycle on
POOL. AfterPOOL alpha betaand threePICKs (alpha,beta,alpha), a newPOOL gamma delta epsilonmust make the nextPICKprintgamma— not continue at the old offset. - Mishandling repeated
RESET. EveryRESETprintsOK, even back-to-back: on poola b, the sequencePICK PICK RESET PICK RESET RESET PICK PICKmust print exactlya b OK a OK OK a b.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…