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

ResourceReason for Pooling
Database connectionsOpening a connection takes noticeable time.
Worker threadsStarting threads repeatedly wastes processor effort.
Game bullets and particlesGames recycle effects instead of allocating memory every frame.
Large buffersBig memory blocks strain the garbage collector.

Advantages and Disadvantages

AdvantagesDisadvantages
Faster response under loadExtra code to manage borrowing
Steady memory useForgotten release() calls drain the pool
Limit on total resourcesStale 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.