← Questions

Thread-safe database connection pool with blocking acquire

0
HarveyCodingLast asked Apr 2026

You are given a mocked database whose connections can run query. Write a connection pool that keeps a fixed set of connections open and lends them out: acquire() hands a caller a connection, and release() gives it back so the next caller can reuse it. The pool will be used from several threads at once.

Interface

Unless you agree on a different interface with the interviewer, assume:

  • MockDB.connect() -> Connection opens a connection (slow); Connection.query(sql: str) -> list runs a query; Connection.close().
  • ConnectionPool(connect: Callable[[], Connection], size: int) manages at most size connections.
  • acquire(timeout: float | None = None) -> Connection returns a free connection, opening a new one only while fewer than size exist. If all are lent out, it blocks until one is released, or raises TimeoutError after timeout seconds.
  • release(conn: Connection) -> None returns a connection. Releasing a connection the pool did not lend, or releasing it twice, raises ValueError.
  • close() -> None closes every idle connection and makes later acquire calls fail.

Example

pool = ConnectionPool(MockDB.connect, size=2)
c1 = pool.acquire()                 # opens connection #1
c2 = pool.acquire()                 # opens connection #2
pool.acquire(timeout=0.1)           ->  TimeoutError     # both are lent out
pool.release(c1)
c3 = pool.acquire()                 # no new connection opened
c3 is c1                            ->  True
pool.release(c2)
pool.release(c2)                    ->  ValueError       # released twice

Constraints

  • size is small (tens); many threads may call acquire and release concurrently.
  • connect() can fail or be slow; a query can raise.

Clarifying questions

  • Should connections be opened up front or lazily on demand?
  • What should acquire do when the pool is exhausted: block, time out, or fail at once?
  • How do we detect a broken connection, and should the pool replace it?
  • Should waiting callers be served in arrival order?

What a strong answer covers

  • An idle queue plus a count of open connections, guarded by one lock and a condition variable (or a bounded queue.Queue and a semaphore).
  • No connection is ever lent to two callers at once, and the total never exceeds size, including when connect() raises (release the slot on failure).
  • Bookkeeping of lent-out connections so a double or foreign release is caught.
  • A context manager (with pool.connection() as conn:) so a connection goes back even when the query raises.
  • Tests with many threads: no more than size connections open, every caller eventually served, timeouts honored.

Follow-ups

  • Validate a connection before lending it and replace dead ones.
  • Close connections idle longer than a limit, and keep a minimum number warm.
  • Make an asyncio version.
  • How would you size the pool for a given database and request rate?
Write and test your solution herePython in your browser, the question's tests level by level, every submission kept for you.Sign in to start coding