PracHub
QuestionsPremiumCoachesLearningGuidesInterview Prep

Quick Overview

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.

  • easy
  • Stripe
  • Coding & Algorithms
  • Software Engineer

Implement stateful connection router simulator

Company: Stripe

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: easy

Interview Round: Take-home Project

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): ```text 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 **new** `connId`: - 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 `connId` is **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 `connId` is 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 `connId` is currently connected to some target: - Remove the mapping for this `connId`. - Decrement the active connection count for that target. - Return `true`. - If `connId` is **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 `maxConnectionsPerTarget` active connections at any time. - When a new connection is requested via `connect(connId)` for a previously unseen `connId`: - 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 `-1` to 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: 1. If the target is **already shut down**, do nothing and return an empty list. 2. Otherwise: - Mark this target as **unavailable** for future `connect` operations. - Collect **all `connId`s 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 `connId`s. - **Eviction order requirement:** - Return evicted connection IDs in the order in which their successful `connect` calls occurred (i.e., connection creation time order on that target). - After a target is shut down: - It must be **skipped** by future `connect` calls (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 return `false` when `connId` is not found). You may assume that targets, once shut down, **never come back up** in this problem variant. --- ### Complexity & Data Structures - Let `Q` be the number of operations (CONNECT / DISCONNECT / SHUTDOWN) and `N` be 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 `connId` to `targetIndex`. - 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.

Quick Answer: 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.

Implement a simplified connection router / load balancer for a multi-tenant server system, built up across five parts. There are `num_targets` target servers indexed `0 .. num_targets-1`, each holding at most `max_per_target` concurrent connections. Process a list of operations sequentially and return a list of results (one entry per operation). Operations are given as `[name, arg]`: - `["connect", connId]` -> returns the assigned target index (int), or `-1` if no target can take it. - `["disconnect", connId]` -> returns `true` if a live connection was closed, else `false`. - `["shutdown", targetIndex]` -> returns the list of evicted connIds (in creation-time order on that target), or `[]` if already shut down. Behavior: **Part 1 - Round-robin connect.** Maintain a round-robin pointer over target indices. For a NEW connId, starting at the pointer, scan cyclically and pick the first usable target; assign it, then advance the pointer to one past the chosen target (mod N). Return the chosen index. **Part 2 - Duplicate connect.** If connId is already connected, return its existing target and change NO state (no reassign, no pointer move, no count change). **Part 3 - Disconnect.** If connId is live, remove its mapping, decrement that target's count, return `true`. Otherwise return `false`. The round-robin pointer is unaffected. **Part 4 - Capacity.** Skip targets already at `max_per_target` during the round-robin scan. If every non-shutdown target is full, ignore the request, change no state, and return `-1`. **Part 5 - Shutdown.** Mark the target unavailable for all future connects (it never comes back), evict every connId currently on it, and return them in the order their successful connects happened on that target. A second shutdown of the same target returns `[]` and changes nothing. After a shutdown, disconnect of an already-evicted connId returns `false`. Return the list of results across all operations.

Constraints

  • 1 <= num_targets
  • 1 <= max_per_target
  • connId values are unique per logical client connection (strings or integers)
  • operations are processed sequentially in a single thread
  • a shutdown target never comes back up
  • shutdown(targetIndex) is only called with a valid index in [0, num_targets-1]

Examples

Input: (3, 5, [['connect', 'a'], ['connect', 'b'], ['connect', 'c'], ['connect', 'd']])

Expected Output: [0, 1, 2, 0]

Explanation: Round-robin over 3 targets with ample capacity: a->0, b->1, c->2, then pointer wraps so d->0.

Input: (3, 5, [['connect', 'a'], ['connect', 'a'], ['connect', 'b']])

Expected Output: [0, 0, 1]

Explanation: Part 2: the duplicate connect('a') returns the existing target 0 and does NOT advance the pointer, so b still lands on 1.

Input: (2, 5, [['connect', 'a'], ['connect', 'b'], ['disconnect', 'a'], ['disconnect', 'a'], ['disconnect', 'zzz']])

Expected Output: [0, 1, true, false, false]

Explanation: Part 3: first disconnect('a') succeeds (true); the second is now unknown (false); disconnect of a never-seen id is also false.

Input: (2, 1, [['connect', 'a'], ['connect', 'b'], ['connect', 'c']])

Expected Output: [0, 1, -1]

Explanation: Part 4: capacity 1 per target fills both targets with a and b; c finds every non-shutdown target full and returns -1 without changing state.

Input: (2, 1, [['connect', 'a'], ['connect', 'b'], ['disconnect', 'a'], ['connect', 'c']])

Expected Output: [0, 1, true, 0]

Explanation: Freeing target 0 via disconnect('a') lets the later connect('c') reuse the now-open slot on target 0.

Input: (3, 2, [['connect', 'a'], ['connect', 'b'], ['connect', 'c'], ['connect', 'd'], ['shutdown', 0], ['connect', 'e']])

Expected Output: [0, 1, 2, 0, ['a', 'd'], 1]

Explanation: Part 5: a->0, b->1, c->2, d wraps to 0 (now holds a,d). shutdown(0) evicts in creation order ['a','d']. Pointer is at 1, target 0 is offline, so e->1.

Input: (2, 5, [['shutdown', 0], ['shutdown', 0], ['connect', 'a'], ['connect', 'b']])

Expected Output: [[], [], 1, 1]

Explanation: Shutting down an empty target evicts nothing; repeating it is a no-op returning []. Target 0 is offline so both a and b route to target 1.

Input: (1, 1, [['connect', 'x'], ['shutdown', 0], ['connect', 'y'], ['disconnect', 'x']])

Expected Output: [0, ['x'], -1, False]

Explanation: Edge case, single target: x->0, shutdown(0) evicts ['x'], now no target is available so y->-1, and disconnect('x') returns false because x was already evicted.

Input: (3, 10, [['connect', 1], ['connect', 2], ['connect', 1], ['disconnect', 2], ['disconnect', 2]])

Expected Output: [0, 1, 0, true, false]

Explanation: Integer connIds: 1->0, 2->1, duplicate connect(1)->0 (no pointer change), disconnect(2) true, repeat disconnect(2) false.

Hints

  1. Keep four pieces of state: a conn_id -> target_index map, a per-target live count, a per-target boolean 'is shut down', and a single round-robin pointer.
  2. For eviction order on shutdown, store each target's members in insertion order (an ordered map / linked hash structure), so you can return evicted ids in connect-time order in O(k).
  3. Make connect idempotent FIRST: if conn_id is already mapped, return immediately and touch nothing (pointer, counts, membership all unchanged).
  4. The round-robin scan must skip BOTH shutdown targets and full targets; only advance the pointer when you actually assign, and only to one-past the chosen target.
Last updated: Jun 21, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,000+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • Assign Reviewers from Changed Files - Stripe (medium)
  • Generate Account Email Notifications - Stripe (medium)
  • Calculate Transaction Fees - Stripe (medium)
  • Build an Account Transfer Ledger - Stripe (medium)
  • Implement Validation and String Compression - Stripe (hard)