Design Patterns Object Pool
The Object Pool pattern keeps a ready-made group of reusable objects and hands them out on request. Callers borrow an object, finish the job, and return it to the pool. Expensive creation work happens once instead of thousands of times.
The Real-World Analogy
A bicycle-sharing station holds a fixed number of bikes. Riders unlock one, travel across town, and dock it again. Nobody builds a new bike for each trip, and the next rider reuses the same frame within minutes.
Problem Without a Pool
Request 1 ──▶ create Connection (slow) ──▶ use ──▶ discard Request 2 ──▶ create Connection (slow) ──▶ use ──▶ discard Request 3 ──▶ create Connection (slow) ──▶ use ──▶ discard Result: time and memory vanish into repeated creation and cleanup.
Some objects cost a lot to build. Database connections, network sockets, and large worker threads fall into this group. Creating them for every request slows the whole application.
Solution With a Pool
┌──────────── POOL ────────────┐
Request 1 ──▶ │ [Conn A] [Conn B] [Conn C] │ ──▶ borrows Conn A
Request 2 ──▶ │ idle objects │ ──▶ borrows Conn B
Request 1 ◀── │ returns Conn A │
Request 3 ──▶ │ │ ──▶ reuses Conn A
└──────────────────────────────┘
The pool builds objects early or on demand, tracks which ones sit idle, and accepts them back after use.
Structure
┌──────────────────────────┐ ┌───────────────────┐ │ ObjectPool │ manages │ Reusable │ ├──────────────────────────┤ ───────▶ ├───────────────────┤ │ - idle: List │ │ + reset() │ │ - busy: List │ │ + doWork() │ │ - maxSize: int │ └───────────────────┘ ├──────────────────────────┤ │ + acquire(): Reusable │ │ + release(Reusable) │ └──────────────────────────┘
Roles
- Reusable: the costly object that clients borrow.
- ObjectPool: the manager that stores, lends, and collects objects.
- Client: the code that calls acquire() and release().
Code Example
class ConnectionPool {
private List idle = new ArrayList();
private int maxSize = 3;
private int created = 0;
public Connection acquire() {
if (!idle.isEmpty()) {
return idle.remove(0); // reuse
}
if (created < maxSize) {
created = created + 1;
return new Connection(); // build only when needed
}
throw new PoolEmptyException(); // all busy
}
public void release(Connection c) {
c.reset(); // clean old state
idle.add(c);
}
}
Connection c = pool.acquire();
c.runQuery("SELECT name FROM students");
pool.release(c);Life Cycle of a Pooled Object
Created ──▶ Idle ──acquire()──▶ Busy ──release()──▶ Idle
│
└── broken? ──▶ Discarded
Every returned object needs a reset. Leftover data from the previous user can leak into the next job and cause confusing bugs.
Where Object Pools Appear
| Resource | Reason for Pooling |
|---|---|
| Database connections | Opening a connection takes noticeable time. |
| Worker threads | Starting threads repeatedly wastes processor effort. |
| Game bullets and particles | Games recycle effects instead of allocating memory every frame. |
| Large buffers | Big memory blocks strain the garbage collector. |
Advantages and Disadvantages
| Advantages | Disadvantages |
|---|---|
| Faster response under load | Extra code to manage borrowing |
| Steady memory use | Forgotten release() calls drain the pool |
| Limit on total resources | Stale state risk after reuse |
When to Use Object Pool
- Choose it when object creation costs more than reuse.
- Choose it when the system needs a hard cap on a resource.
- Skip it for cheap objects, because modern runtimes create small objects very quickly.
Sizing the Pool
A pool that is too small makes callers wait. Oversized pools waste memory and hold resources that nobody uses. Start with a modest size, watch real traffic, and adjust. Many teams track three numbers: how many objects sit idle, how many are busy, and how long callers wait for one.
When the Pool Runs Dry
Designers pick one of three behaviors. The pool can make callers wait for a limited time, create a temporary extra object, or reject the request with a clear message. Waiting with a timeout works well for most applications.
Common Mistakes
- Forgetting to release an object after an error. Place release() inside a cleanup block to guarantee it runs.
- Returning objects that hold private data from the last user.
- Sharing one pooled object between two threads at the same time.
Key Takeaways
- An object pool recycles expensive objects instead of rebuilding them.
- Clients acquire an object, use it, and release it.
- Resetting returned objects prevents data leaks between users.
- Pools shine for connections, threads, and large buffers.
