Simulate sticky load balancer with shutdown
Company: Stripe
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Take-home Project
Quick Answer: This question evaluates implementation skills for stateful simulation and algorithmic reasoning about load balancing, covering connection stickiness, capacity constraints, ordered re-routing on shutdown, and deterministic tie-breaking while testing data structure management and routing logic.
Part 1: Basic Load Balancing
Constraints
- 1 <= m <= 100000
- 0 <= len(requests) <= 200000
- Each request is a valid CONNECT request
- connectionId is a non-empty string
Examples
Input: (3, ['CONNECT a', 'CONNECT b', 'CONNECT c', 'CONNECT d', 'CONNECT e'])
Expected Output: ['a 0', 'b 1', 'c 2', 'd 0', 'e 1']
Explanation: Servers are chosen by minimum load, breaking ties by smaller index.
Input: (1, ['CONNECT x', 'CONNECT y'])
Expected Output: ['x 0', 'y 0']
Explanation: With one server, every connection goes to server 0.
Input: (2, [])
Expected Output: []
Explanation: Edge case: no requests means no log entries.
Input: (4, ['CONNECT p', 'CONNECT q', 'CONNECT r', 'CONNECT s'])
Expected Output: ['p 0', 'q 1', 'r 2', 's 3']
Explanation: The first four connections spread evenly across four servers.
Hints
- Keep track of each server's current load.
- A min-heap of (load, serverIndex) helps you find the next target server quickly.
Part 2: Load Balancer with Disconnect
Constraints
- 1 <= m <= 100000
- 0 <= len(requests) <= 200000
- Active connectionIds are unique at connect time
- If DISCONNECT references an inactive connection, ignore it
Examples
Input: (2, ['CONNECT a', 'CONNECT b', 'DISCONNECT a', 'CONNECT c'])
Expected Output: ['a 0', 'b 1', 'c 0']
Explanation: After 'a' disconnects, server 0 becomes the least loaded again.
Input: (3, ['CONNECT a', 'CONNECT b', 'CONNECT c', 'DISCONNECT b', 'CONNECT d'])
Expected Output: ['a 0', 'b 1', 'c 2', 'd 1']
Explanation: Disconnecting 'b' makes server 1 the best target for 'd'.
Input: (2, ['DISCONNECT ghost'])
Expected Output: []
Explanation: Edge case: disconnecting a missing connection does nothing.
Input: (1, ['CONNECT a', 'DISCONNECT a', 'CONNECT b'])
Expected Output: ['a 0', 'b 0']
Explanation: With one server, both successful connects route to server 0.
Hints
- You need a map from connectionId to its current server.
- After a disconnect, the least-loaded server can change, so update your heap lazily.
Part 3: Sticky Load Balancer by Object
Constraints
- 1 <= m <= 100000
- 0 <= len(requests) <= 200000
- Active connectionIds are unique at connect time
- If all active connections for an objectId are gone, that object loses its sticky assignment
Examples
Input: (3, ['CONNECT c1 o1', 'CONNECT c2 o2', 'CONNECT c3 o1'])
Expected Output: ['c1 0', 'c2 1', 'c3 0']
Explanation: The third request sticks to server 0 because object o1 is already active there.
Input: (2, ['CONNECT a x', 'CONNECT b y', 'CONNECT c z', 'CONNECT d z', 'CONNECT e x', 'DISCONNECT a', 'DISCONNECT e', 'CONNECT f x'])
Expected Output: ['a 0', 'b 1', 'c 0', 'd 0', 'e 0', 'f 1']
Explanation: After both x-connections disconnect, object x loses stickiness and is routed again by load, this time to server 1.
Input: (1, ['DISCONNECT ghost', 'CONNECT p obj', 'CONNECT q obj'])
Expected Output: ['p 0', 'q 0']
Explanation: Edge case: the missing disconnect is ignored, and object stickiness keeps both connections on server 0.
Input: (2, [])
Expected Output: []
Explanation: Edge case: no requests.
Hints
- Track both connectionId -> (server, objectId) and objectId -> server.
- You also need a count of active connections per objectId so you know when to remove sticky state.
Part 4: Sticky Load Balancer with Capacity Limits
Constraints
- 1 <= len(capacity) <= 100000
- 0 <= capacity[i] <= 1000000000
- 0 <= len(requests) <= 200000
- If DISCONNECT references an inactive connection, ignore it
Examples
Input: ([1, 2], ['CONNECT a x', 'CONNECT b y', 'CONNECT c z', 'CONNECT d w'])
Expected Output: ['a 0', 'b 1', 'c 1']
Explanation: Server 0 fills first, then server 1 fills, so the last connect is rejected.
Input: ([1, 2], ['CONNECT a x', 'CONNECT b y', 'CONNECT c x'])
Expected Output: ['a 0', 'b 1']
Explanation: Object x is sticky to server 0, but server 0 is already full, so the third request is rejected.
Input: ([1, 1], ['CONNECT a x', 'CONNECT b y', 'DISCONNECT a', 'CONNECT c z'])
Expected Output: ['a 0', 'b 1', 'c 0']
Explanation: Disconnecting 'a' frees capacity on server 0.
Input: ([0, 2], ['CONNECT a x', 'CONNECT b x', 'CONNECT c y'])
Expected Output: ['a 1', 'b 1']
Explanation: Edge case: server 0 can never accept connections because its capacity is 0.
Hints
- A min-heap still works, but now you must skip servers whose load reached capacity.
- For sticky objects, do not search the heap first; check the required server directly.
Part 5: Sticky Load Balancer with Shutdown
Constraints
- 1 <= len(capacity) <= 100000
- 0 <= capacity[i] <= 1000000000
- 0 <= len(requests) <= 200000
- If DISCONNECT references an inactive connection, ignore it
- For deterministic shutdown behavior, evicted connections must be processed in their arrival order on that server
Examples
Input: ([2, 2, 2], ['CONNECT c1 o1', 'CONNECT c2 o2', 'CONNECT c3 o3', 'CONNECT c4 o4', 'SHUTDOWN 0'])
Expected Output: ['c1 0', 'c2 1', 'c3 2', 'c4 0', 'c1 1', 'c4 2']
Explanation: Server 0 is shut down, so c1 and c4 are evicted and re-routed in their original arrival order.
Input: ([3, 2, 2], ['CONNECT a x', 'CONNECT b y', 'CONNECT c x', 'SHUTDOWN 0'])
Expected Output: ['a 0', 'b 1', 'c 0', 'a 2', 'c 2']
Explanation: Both x-connections are evicted from server 0. After eviction, the first one chooses server 2, and the second sticks to it.
Input: ([3, 1, 2], ['CONNECT a x', 'CONNECT b y', 'CONNECT c x', 'CONNECT d x', 'SHUTDOWN 0'])
Expected Output: ['a 0', 'b 1', 'c 0', 'd 0', 'a 2', 'c 2']
Explanation: Only two of the three evicted x-connections can fit on server 2, so the last one is dropped. The processing order decides which IDs survive.
Input: ([1, 1], ['SHUTDOWN 1', 'DISCONNECT ghost', 'CONNECT a x', 'SHUTDOWN 0', 'CONNECT b y'])
Expected Output: ['a 0', 'a 1', 'b 0']
Explanation: Edge case: shutting down an empty server does nothing. Later, shutting down server 0 moves 'a' to server 1, and server 0 becomes available again.
Hints
- During shutdown, remove every active connection from that server before rerouting begins; then rebuild stickiness as reroutes succeed.
- A per-server queue of arrivals plus version numbers is a practical way to recover the correct eviction order even after disconnects and earlier moves.