Your task is to build a database connection pool that distributes a fixed batch of connections to callers and reclaims them afterward. A mock database connection exposing a query method is supplied; you only need to implement the logic for acquiring and releasing connections.
This exercise has a specific shape:
db.query is given to you. Do not implement actual query processing, network calls, or real database connections.acquire and release methods on the pool.Details like the pool's capacity, what acquire should do when every connection is busy (block, error, timeout), and fairness among waiting callers are not prescribed here, so pin them down with your interviewer. The conventions adopted in this write-up are listed below.
A practical warning: the test cases provided during this round have historically contained bugs. If a provided test conflicts with the stated requirements, flag it to the interviewer rather than silently coding to match the test.
acquire() hands back a connection that no other caller is currently holding.release(conn) gives a previously acquired connection back to the pool.acquire() blocks until one is freed via release.The supplied mock is, in essence, a connection object whose query you invoke but never implement:
class MockConnection:
def __init__(self, conn_id: int) -> None:
self.conn_id = conn_id
def query(self, sql: str) -> str:
# Provided for you; pretend this speaks to a database.
return f"connection {self.conn_id}: executed {sql!r}"
API shape to implement:
class ConnectionPool:
def __init__(self, connections: list[MockConnection]) -> None:...
def acquire(self) -> MockConnection:
"""Return an idle connection, blocking until one is available."""
def release(self, conn: MockConnection) -> None:
"""Return an acquired connection to the pool."""
You are permitted to skip straight to the multithreaded version, but the single-threaded pool establishes the bookkeeping that concurrency must later safeguard.
Maintain two statuses for each connection: idle (waiting in the pool) and in use (handed out to a caller).
acquire() takes an idle connection, marks it in use, and returns it. Without threads yet, encountering an empty pool is simply an error.release(conn) confirms the connection is currently tracked as in use by this pool, then returns it to the idle set.This is the version the interview targets: multiple threads concurrently call acquire, execute conn.query(...), and call release.
acquire() on an exhausted pool blocks until another thread releases a connection.acquire must sleep until signalled, not spin checking the idle list repeatedly._idle, _in_use) is protected by a lock you manage directly.The standard tool here is a condition variable paired with a mutex:
acquire waits on the condition while the idle list is empty. Waiting releases the lock so other threads can make progress, and re-acquires it upon waking.release returns the connection to the pool and notifies one waiter.Employ Condition.wait_for(predicate) rather than a bare wait() guarded by if: wake-ups must be re-checked in a loop because another thread may snatch the freed connection before the awakened thread gets it (and spurious wake-ups are permitted by the threading model). wait_for performs that loop on your behalf.
[object Object]