# Object Pool

## Problem

Creating and destroying certain objects is **expensive** — database connections, thread
contexts, GPU buffers, or network sockets. Allocating a new one for every request wastes
resources and adds latency. Destroying them prematurely means repeating that cost.

## Solution

Maintain a **pool** of pre-initialized, reusable objects. Clients **acquire** an object from
the pool, use it, and **release** it back. The pool manages the lifecycle, limiting concurrency
and recycling resources.

```
┌─────────────┐    acquire()   ┌──────────────────────┐
│   Client     │──────────────>│   ConnectionPool      │
│              │<──────────────│   [conn1, conn2, ...] │
│  uses conn   │   release()   │   maxSize: 3          │
└─────────────┘───────────────>└──────────────────────┘
```

## When to Use

- Object creation is expensive (>1ms or involves I/O).
- You need many short-lived usages of the same resource type.
- You want to bound the number of concurrent resources (e.g., max 10 DB connections).

## When to Avoid

- Objects are cheap to create — the pool overhead isn't justified.
- Objects hold state that can't be safely reset between uses.
- Usage duration is long and unpredictable — the pool becomes a bottleneck.

## Pseudocode

```
class ConnectionPool:
    fields: available[], inUse[], maxSize

    constructor(maxSize):
        this.maxSize = maxSize
        available = []
        inUse = []

    method acquire() -> Connection:
        if available is not empty:
            conn = available.pop()
        else if inUse.size < maxSize:
            conn = createNewConnection()
        else:
            wait until one is released
            conn = available.pop()
        conn.reset()
        inUse.add(conn)
        return conn

    method release(conn):
        inUse.remove(conn)
        available.add(conn)

// Usage
pool = ConnectionPool(maxSize=3)
conn = pool.acquire()
conn.execute("SELECT 1")
pool.release(conn)
```

## Python

```python
"""Object Pool — Connection pool example."""

import threading
import time


class Connection:
    """Simulates an expensive-to-create resource."""

    _counter = 0

    def __init__(self) -> None:
        Connection._counter += 1
        self.id = Connection._counter
        # Simulate expensive creation
        time.sleep(0.05)
        print(f"  [Connection {self.id}] Created (expensive!)")

    def execute(self, query: str) -> str:
        result = f"Result of '{query}' from conn-{self.id}"
        print(f"  [Connection {self.id}] {result}")
        return result

    def reset(self) -> None:
        """Reset connection state for reuse."""
        pass


class ConnectionPool:
    """Thread-safe object pool for Connection instances."""

    def __init__(self, max_size: int = 3) -> None:
        self._max_size = max_size
        self._available: list[Connection] = []
        self._in_use: set[int] = set()
        self._lock = threading.Lock()
        self._semaphore = threading.Semaphore(max_size)

    def acquire(self) -> Connection:
        self._semaphore.acquire()
        with self._lock:
            if self._available:
                conn = self._available.pop()
                print(f"  [Pool] Reusing connection {conn.id}")
            else:
                conn = Connection()
            conn.reset()
            self._in_use.add(conn.id)
            return conn

    def release(self, conn: Connection) -> None:
        with self._lock:
            self._in_use.discard(conn.id)
            self._available.append(conn)
            print(f"  [Pool] Released connection {conn.id} "
                  f"(available: {len(self._available)})")
        self._semaphore.release()

    @property
    def stats(self) -> str:
        return (f"Pool(available={len(self._available)}, "
                f"in_use={len(self._in_use)}, max={self._max_size})")


if __name__ == "__main__":
    pool = ConnectionPool(max_size=2)
    print(f"Initial: {pool.stats}\n")

    # Acquire, use, release
    print("--- Round 1: Two connections ---")
    c1 = pool.acquire()
    c2 = pool.acquire()
    c1.execute("SELECT 1")
    c2.execute("SELECT 2")
    pool.release(c1)
    pool.release(c2)
    print(f"After round 1: {pool.stats}\n")

    # Reuse existing connections
    print("--- Round 2: Reusing connections ---")
    c3 = pool.acquire()
    c3.execute("SELECT 3")
    c4 = pool.acquire()
    c4.execute("SELECT 4")
    pool.release(c3)
    pool.release(c4)
    print(f"After round 2: {pool.stats}")

    print("\n--- Object pool demo complete ---")
```

## Go

```go
// object_pool.go — Connection pool example
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

// Connection simulates an expensive resource.
type Connection struct {
	ID int32
}

var connCounter int32

func NewConnection() *Connection {
	id := atomic.AddInt32(&connCounter, 1)
	// Simulate expensive creation
	time.Sleep(50 * time.Millisecond)
	fmt.Printf("  [Connection %d] Created (expensive!)\n", id)
	return &Connection{ID: id}
}

func (c *Connection) Execute(query string) string {
	result := fmt.Sprintf("Result of '%s' from conn-%d", query, c.ID)
	fmt.Printf("  [Connection %d] %s\n", c.ID, result)
	return result
}

func (c *Connection) Reset() {}

// ConnectionPool is a bounded, thread-safe object pool.
type ConnectionPool struct {
	pool chan *Connection
	mu   sync.Mutex
	max  int
	used int
}

func NewConnectionPool(maxSize int) *ConnectionPool {
	return &ConnectionPool{
		pool: make(chan *Connection, maxSize),
		max:  maxSize,
	}
}

func (p *ConnectionPool) Acquire() *Connection {
	select {
	case conn := <-p.pool:
		fmt.Printf("  [Pool] Reusing connection %d\n", conn.ID)
		conn.Reset()
		p.mu.Lock()
		p.used++
		p.mu.Unlock()
		return conn
	default:
		p.mu.Lock()
		if p.used < p.max {
			p.used++
			p.mu.Unlock()
			return NewConnection()
		}
		p.mu.Unlock()
		// Block until one is available
		conn := <-p.pool
		fmt.Printf("  [Pool] Reusing connection %d\n", conn.ID)
		conn.Reset()
		p.mu.Lock()
		p.used++
		p.mu.Unlock()
		return conn
	}
}

func (p *ConnectionPool) Release(conn *Connection) {
	p.mu.Lock()
	p.used--
	p.mu.Unlock()
	fmt.Printf("  [Pool] Released connection %d\n", conn.ID)
	p.pool <- conn
}

func (p *ConnectionPool) Stats() string {
	p.mu.Lock()
	defer p.mu.Unlock()
	return fmt.Sprintf("Pool(available=%d, in_use=%d, max=%d)",
		len(p.pool), p.used, p.max)
}

func main() {
	pool := NewConnectionPool(2)
	fmt.Printf("Initial: %s\n\n", pool.Stats())

	// Acquire, use, release
	fmt.Println("--- Round 1: Two connections ---")
	c1 := pool.Acquire()
	c2 := pool.Acquire()
	c1.Execute("SELECT 1")
	c2.Execute("SELECT 2")
	pool.Release(c1)
	pool.Release(c2)
	fmt.Printf("After round 1: %s\n\n", pool.Stats())

	// Reuse existing connections
	fmt.Println("--- Round 2: Reusing connections ---")
	c3 := pool.Acquire()
	c3.Execute("SELECT 3")
	c4 := pool.Acquire()
	c4.Execute("SELECT 4")
	pool.Release(c3)
	pool.Release(c4)
	fmt.Printf("After round 2: %s\n", pool.Stats())

	fmt.Println("\n--- Object pool demo complete ---")
}
```

## JavaScript

```javascript
// object_pool.js — Connection pool example

let connectionCounter = 0;

class Connection {
  constructor() {
    this.id = ++connectionCounter;
    console.log(`  [Connection ${this.id}] Created (expensive!)`);
  }

  execute(query) {
    const result = `Result of '${query}' from conn-${this.id}`;
    console.log(`  [Connection ${this.id}] ${result}`);
    return result;
  }

  reset() {
    // Reset connection state for reuse
  }
}

class ConnectionPool {
  constructor(maxSize = 3) {
    this._maxSize = maxSize;
    this._available = [];
    this._inUse = new Set();
    this._waitQueue = [];
  }

  acquire() {
    if (this._available.length > 0) {
      const conn = this._available.pop();
      console.log(`  [Pool] Reusing connection ${conn.id}`);
      conn.reset();
      this._inUse.add(conn.id);
      return conn;
    }
    if (this._inUse.size < this._maxSize) {
      const conn = new Connection();
      this._inUse.add(conn.id);
      return conn;
    }
    // In a real async implementation, you'd return a Promise here.
    throw new Error("Pool exhausted — all connections in use");
  }

  release(conn) {
    this._inUse.delete(conn.id);
    this._available.push(conn);
    console.log(
      `  [Pool] Released connection ${conn.id} (available: ${this._available.length})`,
    );
  }

  get stats() {
    return `Pool(available=${this._available.length}, in_use=${this._inUse.size}, max=${this._maxSize})`;
  }
}

// --- Demo ---

const pool = new ConnectionPool(2);
console.log(`Initial: ${pool.stats}\n`);

// Round 1: create and use
console.log("--- Round 1: Two connections ---");
const c1 = pool.acquire();
const c2 = pool.acquire();
c1.execute("SELECT 1");
c2.execute("SELECT 2");
pool.release(c1);
pool.release(c2);
console.log(`After round 1: ${pool.stats}\n`);

// Round 2: reuse
console.log("--- Round 2: Reusing connections ---");
const c3 = pool.acquire();
c3.execute("SELECT 3");
const c4 = pool.acquire();
c4.execute("SELECT 4");
pool.release(c3);
pool.release(c4);
console.log(`After round 2: ${pool.stats}`);

console.log("\n--- Object pool demo complete ---");
```

## Related Patterns

- **Singleton** — the pool itself is typically a singleton managing the shared resource set.
- **Flyweight** — flyweight shares immutable intrinsic state across many objects; object pool recycles mutable, stateful resources.
- **Proxy** — a proxy can implement checkout/checkin semantics, wrapping a pooled resource and returning it on release.
