Implement round-robin load balancer
Company: DoorDash
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
Quick Answer: This question evaluates a candidate's ability to implement a strict round-robin load balancer with O(1) average operations while handling concurrent nextServer and update calls, health-check-driven skipping, and failure scenarios, testing competencies in data structures, concurrent programming, and fault-tolerant system behavior.
Constraints
- 0 <= number of operations <= 10^5
- Server ids are unique strings; add of an existing id is a no-op
- remove/mark of an absent id is a no-op
- Newly added servers start healthy
- next returns None when there is no currently-healthy server
Examples
Input: ([('add', 'a'), ('add', 'b'), ('add', 'c'), ('next',), ('next',), ('next',), ('next',)],)
Expected Output: ['a', 'b', 'c', 'a']
Explanation: Three healthy servers rotate strictly a->b->c, then wrap back to a.
Input: ([('add', 'a'), ('add', 'b'), ('add', 'c'), ('mark', 'b', False), ('next',), ('next',), ('next',)],)
Expected Output: ['a', 'c', 'a']
Explanation: b is unhealthy and is skipped; rotation is a->c->a while preserving order.
Input: ([('add', 'a'), ('add', 'b'), ('add', 'c'), ('next',), ('remove', 'b'), ('next',), ('next',)],)
Expected Output: ['a', 'c', 'a']
Explanation: First next returns a (pointer now at b). Removing b shifts the rotation; next continues at c, then wraps to a.
Input: ([('next',)],)
Expected Output: [None]
Explanation: No servers registered, so next yields None.
Input: ([('add', 'a'), ('mark', 'a', False), ('next',), ('mark', 'a', True), ('next',)],)
Expected Output: [None, 'a']
Explanation: The only server is unhealthy (None), then made healthy again so next returns a.
Input: ([('add', 'x'), ('add', 'y'), ('next',), ('next',), ('next',), ('next',)],)
Expected Output: ['x', 'y', 'x', 'y']
Explanation: Two servers alternate fairly over four calls.
Input: ([('add', 'a'), ('add', 'b'), ('add', 'c'), ('mark', 'a', False), ('mark', 'b', False), ('mark', 'c', False), ('next',)],)
Expected Output: [None]
Explanation: All servers unhealthy, so next returns None instead of an infinite scan.
Hints
- Keep an ordered list of server ids to define rotation order, plus a hash map id -> isHealthy so add/mark are O(1) average.
- Store a single integer pointer for the next candidate. On next, scan forward (with modular wraparound) and pick the first healthy server, then set the pointer just past it.
- To keep fairness across removals, when you delete a server before the current pointer, decrement the pointer; then re-normalize it modulo the new size so it never points out of range.
- Thread-safety in a real system: guard the structure with a single mutex (or a read-write lock favoring next). For O(1) lock-free fairness you can use an atomic counter and a copy-on-write snapshot of the healthy-server array.