Implement stateful connection router simulator
StripeSoftware Engineer · Online AssessmentLast updated
This question evaluates design and implementation skills for a stateful connection router, focusing on round-robin load distribution, connection lifecycle handling (duplicate connects, disconnects, shutdowns), and per-target capacity and mapping management.
You are implementing a simplified connection router / load balancer for a Jupyter-like multi-tenant server system.
There are N target servers, indexed from 0 to N - 1.
Each target server can maintain at most maxConnectionsPerTarget concurrent connections.
You will implement a small, stateful simulator that processes a sequence of commands and updates internal state accordingly.
Assume all commands are processed sequentially in a single thread.
Connection IDs (connId) are arbitrary identifiers (e.g., strings or integers) and are unique per logical client connection.
Design an API like the following (you can adapt naming / exact signatures to your chosen language):
class ConnectionRouter {
ConnectionRouter(int numTargets, int maxConnectionsPerTarget)
// Part 1 & 2: connection handling
int connect(connId)
// Part 3: disconnect handling
boolean disconnect(connId)
// Part 5: shutdown handling
List<connId> shutdown(int targetIndex)
}
You must support the following behavior, built up in five parts.
Part 1: Basic CONNECT routing (round-robin)
Implement connect(connId) for the basic case (ignore duplicates, DISCONNECT, and SHUTDOWN for now).
- Maintain a round-robin pointer over target indices
[0, 1, ..., N-1]. - When
connect(connId)is called for a newconnId: - Starting from the current round-robin pointer, scan targets in cyclic order (0 → 1 → ... → N-1 → 0 → ...).
- Select the first target that is:
- not shut down (in Part 1, assume all are up), and
- has free capacity (in Part 1, assume effectively unlimited capacity; capacity will be enforced in Part 4).
- Assign the connection to that target.
- Advance the round-robin pointer to the next index after the chosen target (modulo
N). - Return the chosen
targetIndex.
For now, it is acceptable to assume all connections are unique and never disconnected. You should still design your internal data structures anticipating later parts.
Part 2: Handle duplicate CONNECT calls
Extend connect(connId) to handle duplicate connections with the same connId:
- If
connIdis already connected to some target: - Treat this as a duplicate CONNECT.
- Do not change any internal state:
- Do not reassign the target.
- Do not change the round-robin pointer.
- Do not change any connection counts.
- Simply return the existing target index for this
connId. - Only if
connIdis not currently connected should you perform a new assignment using the Part 1 logic.
You will therefore need to maintain a mapping from connId to its current targetIndex.
Part 3: Support DISCONNECT
Implement disconnect(connId) to explicitly close a connection:
- If
connIdis currently connected to some target: - Remove the mapping for this
connId. - Decrement the active connection count for that target.
- Return
true. - If
connIdis not currently connected (unknown or already disconnected): - Do nothing.
- Return
false.
The round-robin pointer is not affected by disconnect operations.
Part 4: Enforce maxConnectionsPerTarget capacity
Now fully enforce per-target capacity via the constructor parameter maxConnectionsPerTarget.
- Each target can have at most
maxConnectionsPerTargetactive connections at any time. - When a new connection is requested via
connect(connId)for a previously unseenconnId: - Use the round-robin scan as in Part 1, but skip any targets that are already at capacity.
- If you find a target that is not shut down and has capacity:
- Assign the connection there.
- Increment that target's connection count.
- Advance the round-robin pointer to just after that target (modulo
N). - Return that
targetIndex. - If all non-shutdown targets are at capacity, then ignore the connect request:
- Do not create a connection.
- Do not change any state (including the round-robin pointer).
- Return a sentinel value such as
-1to indicate that no target could be assigned.
Duplicate connect(connId) calls (where connId is already connected) still behave as in Part 2: return the existing target and do not change state.
Part 5: SHUTDOWN target and evict its connections
Implement shutdown(targetIndex) to temporarily take a target offline and evict all connections currently on that target.
- When
shutdown(targetIndex)is called:
- If the target is already shut down, do nothing and return an empty list.
- Otherwise:
-
Mark this target as unavailable for future
connectoperations. -
Collect all
connIds currently assigned to this target. -
For each such
connId: -
Remove its mapping from
connId → targetIndex. -
Decrement the connection count for this target accordingly.
-
Return the list of evicted
connIds. -
Eviction order requirement:
-
Return evicted connection IDs in the order in which their successful
connectcalls occurred (i.e., connection creation time order on that target). -
After a target is shut down:
-
It must be skipped by future
connectcalls (treated as unavailable), regardless of its current connection count. -
disconnect(connId)should still behave correctly even if the connection was already evicted by a shutdown (i.e., just returnfalsewhenconnIdis not found).
You may assume that targets, once shut down, never come back up in this problem variant.
Complexity & Data Structures
- Let
Qbe the number of operations (CONNECT / DISCONNECT / SHUTDOWN) andNbe the number of targets. - Aim for amortized O(1) or O(log N) time per operation.
- Discuss and justify the data structures you choose to:
- Map
connIdtotargetIndex. - Track per-target connection counts and the connections assigned to each target in insertion order.
- Implement the round-robin selection over available, non-full, non-shutdown targets.
Implement the full ConnectionRouter with correct behavior across all five parts.
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.