LLD: Design a Connection Pool with an Internal Request
It will focus on thread safety,fairness vs internal queue,boundness resources and clean separation of responsibilities
Functional
Maintain a pool of reusable connections
Allow concurrent threads to acquire connections
Block or queue requests when no connection is available
Support timeout on acquire
Release connections back to the pool
Validate connections before reuse
Support graceful shutdown
Non-Functional
Thread-safe
Fair ordering (FIFO)
High throughput
No connection leaks
Configurable pool size
2️⃣ High-Level Design
Client Threads
|
v
+---------------------+
| Connection Pool |
|---------------------|
| - availableConns |
| - inUseConns |
| - requestQueue |
| - maxPoolSize |
+---------------------+
|
v
+---------------------+
| Connection Factory |
+---------------------+
3️⃣ Core Components
ComponentResponsibilityConnectionRepresents DB / external connectionPooledConnectionWraps connection with metadataConnectionFactoryCreates new connectionsConnectionPoolManages pooling & request queueAcquireRequestRepresents waiting request
4️⃣ Class Diagram (Conceptual)
ConnectionPool
├── BlockingQueue<PooledConnection> available
├── Set<PooledConnection> inUse
├── BlockingQueue<AcquireRequest> requestQueue
├── ConnectionFactory factory
└── acquire(), release()
AcquireRequest
├── CountDownLatch latch
├── PooledConnection assigned
5️⃣ Java LLD – Code
🔹 Connection Interface
public interface Connection {
void open();
void close();
boolean isValid();
}
🔹 Simple Connection Implementation
public class DBConnection implements Connection {
private boolean open = false;
@Override
public void open() {
open = true;
}
@Override
public void close() {
open = false;
}
@Override
public boolean isValid() {
return open;
}
}
🔹 PooledConnection
public class PooledConnection {
private final Connection connection;
public PooledConnection(Connection connection) {
this.connection = connection;
this.connection.open();
}
public Connection getConnection() {
return connection;
}
public boolean isValid() {
return connection.isValid();
}
public void close() {
connection.close();
}
}
🔹 ConnectionFactory
public interface ConnectionFactory {
PooledConnection create();
}
public class DBConnectionFactory implements ConnectionFactory {
@Override
public PooledConnection create() {
return new PooledConnection(new DBConnection());
}
}
🔹 AcquireRequest (Internal Queue Entry)
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class AcquireRequest {
private final CountDownLatch latch = new CountDownLatch(1);
private PooledConnection assignedConnection;
public void assign(PooledConnection connection) {
this.assignedConnection = connection;
latch.countDown();
}
public PooledConnection await(long timeoutMs) throws InterruptedException {
boolean success = latch.await(timeoutMs, TimeUnit.MILLISECONDS);
return success ? assignedConnection : null;
}
}
🔹 ConnectionPool (Core Logic)
import java.util.*;
import java.util.concurrent.*;
public class ConnectionPool {
private final int maxPoolSize;
private final ConnectionFactory factory;
private final BlockingQueue<PooledConnection> available;
private final Set<PooledConnection> inUse;
private final BlockingQueue<AcquireRequest> requestQueue;
public ConnectionPool(int maxPoolSize, ConnectionFactory factory) {
this.maxPoolSize = maxPoolSize;
this.factory = factory;
this.available = new LinkedBlockingQueue<>();
this.inUse = ConcurrentHashMap.newKeySet();
this.requestQueue = new LinkedBlockingQueue<>();
}
public PooledConnection acquire(long timeoutMs) throws InterruptedException {
// Fast path
PooledConnection conn = available.poll();
if (conn != null && conn.isValid()) {
inUse.add(conn);
return conn;
}
synchronized (this) {
if (totalConnections() < maxPoolSize) {
PooledConnection newConn = factory.create();
inUse.add(newConn);
return newConn;
}
}
// Slow path – queue request
AcquireRequest request = new AcquireRequest();
requestQueue.offer(request);
PooledConnection assigned = request.await(timeoutMs);
if (assigned == null) {
requestQueue.remove(request);
throw new RuntimeException("Timeout waiting for connection");
}
inUse.add(assigned);
return assigned;
}
public void release(PooledConnection connection) {
if (connection == null) return;
inUse.remove(connection);
AcquireRequest pending = requestQueue.poll();
if (pending != null) {
pending.assign(connection);
} else {
available.offer(connection);
}
}
private int totalConnections() {
return available.size() + inUse.size();
}
}
6️⃣ Example Usage
public class Main {
public static void main(String[] args) throws InterruptedException {
ConnectionPool pool = new ConnectionPool(
5,
new DBConnectionFactory()
);
Runnable task = () -> {
try {
PooledConnection conn = pool.acquire(2000);
System.out.println(Thread.currentThread().getName() + " acquired connection");
Thread.sleep(1000);
pool.release(conn);
} catch (Exception e) {
e.printStackTrace();
}
};
for (int i = 0; i < 10; i++) {
new Thread(task).start();
}
}
}
7️⃣ Why Internal Request Queue Is Important (Interview Gold)
✅ Prevents thread starvation
✅ Ensures FIFO fairness
✅ Avoids busy-waiting
✅ Enables timeouts
✅ Scales better than synchronized waits
8️⃣ Possible Extensions (Mention in Interview)
Connection eviction (idle timeout)
Health check thread
Metrics (wait time, saturation)
Priority queues
Circuit breaker integration
Async acquire API (CompletableFuture)
9️⃣ One-Line Summary (Interview)
“This connection pool uses a bounded pool with a FIFO internal request queue to ensure fairness, avoid thread starvation, and provide timeout-based acquisition under contention.”

