# Pool

A `Pool<A, E>` owns a bounded set of **expensive, scoped resources** — database
connections, HTTP clients, buffers, anything where acquisition is costly and
total concurrency must be capped. Instead of each fiber acquiring and releasing
its own resource, fibers **borrow** a shared resource from the pool and return
it automatically.

The pool acquires each item with a scoped effect (`Effect<A, E, Scope>`), keeps
items alive between a minimum and maximum size, and releases everything when the
pool's own scope closes. Borrowing is done with [`Pool.get`](#get), which checks
out an item **scoped to the caller** — when that scope closes, the item goes
back into the pool for reuse.
**What a pool is built from:** A `Pool` is assembled from primitives you may already know: a
  [`Scope`](https://effect.plants.sh/resource-management/scope-and-finalizers/) governs the pool's
  lifetime and each item's lifetime, a
  [`Semaphore`](https://effect.plants.sh/concurrency/semaphore-and-latch/) bounds the total number of
  concurrent checkouts, and a `Latch` parks waiters until an item becomes
  available. You don't interact with these directly, but they explain the
  semantics: a closed pool scope tears everything down, and `get` *waits* rather
  than over-allocating when the pool is at capacity.

## Why a pool

Imagine a resource that takes real time and money to set up — a database
connection. Without a pool, 100 concurrent requests would open 100 connections.
A pool lets those 100 requests share, say, 10 connections: the 11th request
waits for one of the first 10 to be returned.

```ts
import { Effect, Pool, Scope } from "effect"

interface Connection {
  readonly execute: (sql: string) => Effect.Effect<ReadonlyArray<string>>
  readonly close: Effect.Effect<void>
}

// `acquire` is a *scoped* effect: it acquires a connection and registers its
// own release. The pool runs this once per item it keeps alive.
const acquireConnection: Effect.Effect<Connection, never, Scope.Scope> =
  Effect.acquireRelease(
    Effect.sync(() => ({
      execute: (sql: string) => Effect.succeed([`executed: ${sql}`]),
      close: Effect.void
    })),
    (conn) => conn.close
  )
```

## Creating a fixed-size pool and borrowing

The simplest pool is fixed-size: [`Pool.make`](#make) keeps exactly `size`
items. Borrow one with [`Pool.get`](#get). Because `get` returns an
`Effect<A, E, Scope>`, the borrowed item is **returned to the pool when the
borrowing scope closes** — you never call a `release` yourself.

```ts
import { Effect, Pool } from "effect"

const program = Effect.gen(function*() {
  // `make` itself needs a Scope: closing that scope shuts the pool down.
  const pool = yield* Pool.make({
    acquire: acquireConnection,
    size: 10
  })

  // Borrow a connection. `Effect.scoped` opens a scope for the checkout, so the
  // connection is returned to the pool the moment this block finishes.
  const rows = yield* Effect.scoped(
    Effect.gen(function*() {
      const conn = yield* Pool.get(pool)
      return yield* conn.execute("select * from users")
    })
  )

  return rows
  // => ["executed: select * from users"]
})

// `Effect.scoped` here owns the *pool's* lifetime: connections close on exit.
Effect.runPromise(Effect.scoped(program))
```

<Aside type="caution" title="Don't leak the checkout scope">
  An item is returned to the pool only when its checkout scope closes. If you
  borrow inside a long-lived scope and hold it, that item stays out of
  circulation. Keep checkout scopes as small as the work that needs the
  resource.
</Aside>

### Bounding concurrent use per item

`concurrency` controls how many fibers may use a **single** item at once
(default `1`). A pool with `size: 4` and `concurrency: 2` can serve up to **8**
simultaneous checkouts before callers start waiting. This is useful when a
single resource (such as an HTTP client with connection multiplexing) safely
supports several concurrent operations.

```ts
import { Effect, Pool } from "effect"

const pool = Pool.make({
  acquire: acquireConnection,
  size: 4,
  concurrency: 2 // up to 4 * 2 = 8 concurrent checkouts
})
```

## Dynamic sizing with makeWithTTL

When load is spiky, a fixed size is wasteful (idle resources) or limiting (too
few under burst). [`Pool.makeWithTTL`](#makewithttl) grows the pool up to `max`
under load and reclaims **excess** idle items after a `timeToLive`, shrinking
back toward `min`.

```ts
import { Duration, Effect, Pool } from "effect"

const program = Effect.gen(function*() {
  const pool = yield* Pool.makeWithTTL({
    acquire: acquireConnection,
    min: 2, // always keep at least 2 connections warm
    max: 20, // never exceed 20
    timeToLive: Duration.seconds(60) // reclaim idle excess after 60s
  })

  const conn = yield* Pool.get(pool)
  return yield* conn.execute("select 1")
})

Effect.runPromise(Effect.scoped(program))
```

`timeToLiveStrategy` chooses how the TTL is measured:

- `"usage"` (default) — excess items expire relative to overall pool *usage*.
- `"creation"` — each item expires a fixed `timeToLive` after it was *created*.

```ts
const pool = Pool.makeWithTTL({
  acquire: acquireConnection,
  min: 2,
  max: 20,
  timeToLive: Duration.minutes(5),
  timeToLiveStrategy: "creation"
})
```

`targetUtilization` (a value in `(0, 1]`, default `1`) decides how eagerly the
pool grows. At `1`, new items are created only once existing items are fully
utilized; at `0.5`, new items are created when existing ones reach 50%
utilization, trading more resources for lower latency under load.

## Invalidating a broken resource

If you discover a borrowed item is unhealthy — a dropped connection, a failed
health check — tell the pool to discard it with [`Pool.invalidate`](#invalidate).
The item is finalized once it is no longer checked out, and the pool allocates a
replacement on demand.

```ts
import { Effect, Pool } from "effect"

const useConnection = Effect.gen(function*() {
  const pool = yield* Pool.make({ acquire: acquireConnection, size: 10 })

  yield* Effect.scoped(
    Effect.gen(function*() {
      const conn = yield* Pool.get(pool)
      const result = yield* conn.execute("ping").pipe(
        Effect.catchCause(() =>
          // Connection looks broken: discard it instead of returning it.
          Effect.as(Pool.invalidate(pool, conn), ["error"] as ReadonlyArray<string>)
        )
      )
      return result
    })
  )
})
```
**Identity matters:** `invalidate` matches the item by **strict reference equality** (`===`). Pass
  the exact value you got from `get`, not an equivalent copy, or nothing happens.

## Real-world: a pooled service via Layer

In an application you rarely thread a `Pool` around by hand. Instead, build it
once inside a [`Context.Service`](https://effect.plants.sh/services-and-layers/services/) and expose it
behind a `Layer`.
`Layer.effect` runs construction in the layer's own scope, so the pool (and all
its connections) is torn down when the layer closes. Expose a `withConnection`
helper, written with `Effect.fn` since it returns an Effect.

```ts
import { Context, Effect, Layer, Pool, Scope } from "effect"

interface Connection {
  readonly execute: (sql: string) => Effect.Effect<ReadonlyArray<string>>
  readonly close: Effect.Effect<void>
}

const acquireConnection: Effect.Effect<Connection, never, Scope.Scope> =
  Effect.acquireRelease(
    Effect.sync(() => ({
      execute: (sql: string) => Effect.succeed([`executed: ${sql}`]),
      close: Effect.void
    })),
    (conn) => conn.close
  )

class Database extends Context.Service<Database, {
  // `withConnection` borrows a connection for the duration of `use`,
  // then returns it to the pool automatically.
  readonly withConnection: <A, E, R>(
    use: (conn: Connection) => Effect.Effect<A, E, R>
  ) => Effect.Effect<A, E, R>
}>()("app/Database") {
  // `Layer.effect` provides a Scope for construction and removes it from the
  // result, so the pool lives exactly as long as this layer.
  static layer = Layer.effect(
    Database,
    Effect.gen(function*() {
      const pool = yield* Pool.makeWithTTL({
        acquire: acquireConnection,
        min: 2,
        max: 10,
        timeToLive: "1 minute"
      })

      return Database.of({
        withConnection: Effect.fn("Database.withConnection")(
          (use) => Effect.scoped(Effect.flatMap(Pool.get(pool), use))
        )
      })
    })
  )
}

// Callers depend on the service, never on the pool directly.
const query = Effect.gen(function*() {
  const db = yield* Database
  return yield* db.withConnection((conn) => conn.execute("select * from orders"))
})

Effect.runPromise(query.pipe(Effect.provide(Database.layer)))
// => ["executed: select * from orders"]
```

---

## API reference

Every public export of the `Pool` module, with a runnable snippet.

### make

Creates a **fixed-size** pool that keeps exactly `size` items (min equals max,
no growth or shrink). Optional `concurrency` and `targetUtilization` tune
per-item sharing. Returns `Effect<Pool<A, E>, never, R | Scope>` — it needs a
`Scope` whose closure shuts the pool down.

```ts
import { Effect, Pool } from "effect"

const program = Effect.gen(function*() {
  const pool = yield* Pool.make({
    acquire: acquireConnection,
    size: 5
  })
  return yield* Effect.scoped(
    Effect.flatMap(Pool.get(pool), (conn) => conn.execute("select 1"))
  )
  // => ["executed: select 1"]
})

Effect.runPromise(Effect.scoped(program))
```

### makeWithTTL

Creates an **elastic** pool with `min`/`max` bounds that grows under load and
reclaims excess idle items after `timeToLive`. `timeToLiveStrategy` is
`"usage"` (default) or `"creation"`.

```ts
import { Duration, Effect, Pool } from "effect"

const program = Effect.gen(function*() {
  const pool = yield* Pool.makeWithTTL({
    acquire: acquireConnection,
    min: 1,
    max: 8,
    timeToLive: Duration.seconds(30)
  })
  return yield* Effect.scoped(
    Effect.flatMap(Pool.get(pool), (conn) => conn.execute("select now()"))
  )
  // => ["executed: select now()"]
})

Effect.runPromise(Effect.scoped(program))
```

### makeWithStrategy

Creates a pool driven by a custom [`Strategy`](#strategy), giving you full
control over background resizing and item reclamation. `make` and `makeWithTTL`
are both thin wrappers over this (a no-op strategy and TTL strategies,
respectively). Use it only when the built-in policies are insufficient.

```ts
import { Effect, Pool } from "effect"

// A strategy that does no background resizing and never reclaims —
// equivalent to what `make` uses internally.
const noopStrategy: Pool.Strategy<Connection, never> = {
  run: () => Effect.void,
  onAcquire: () => Effect.void,
  reclaim: () => Effect.succeed(undefined)
}

const program = Effect.gen(function*() {
  const pool = yield* Pool.makeWithStrategy({
    acquire: acquireConnection,
    min: 2,
    max: 4,
    strategy: noopStrategy
  })
  return yield* Effect.scoped(
    Effect.flatMap(Pool.get(pool), (conn) => conn.execute("ping"))
  )
  // => ["executed: ping"]
})

Effect.runPromise(Effect.scoped(program))
```

### get

Borrows one item from the pool as a scoped effect: `Effect<A, E, Scope>`. The
item is returned to the pool when the surrounding scope closes. If the pool is
at capacity, the effect **waits** for an item; if acquisition fails, the effect
fails with `E` (a later `get` can retry).

```ts
import { Effect, Pool } from "effect"

const program = Effect.gen(function*() {
  const pool = yield* Pool.make({ acquire: acquireConnection, size: 2 })

  yield* Effect.scoped(
    Effect.gen(function*() {
      const conn = yield* Pool.get(pool) // borrowed here
      yield* conn.execute("select 1")
    }) // connection returned to the pool here
  )
})

Effect.runPromise(Effect.scoped(program))
```

### invalidate

Marks a checked-out item as unusable so the pool discards and (lazily) replaces
it instead of returning it for reuse. Returns `Effect<void, never, Scope>`. The
item is matched by reference equality.

```ts
import { Effect, Pool } from "effect"

const program = Effect.gen(function*() {
  const pool = yield* Pool.make({ acquire: acquireConnection, size: 3 })

  yield* Effect.scoped(
    Effect.gen(function*() {
      const conn = yield* Pool.get(pool)
      // Decide the connection is stale and should not be reused.
      yield* Pool.invalidate(pool, conn)
    })
  )
})

Effect.runPromise(Effect.scoped(program))
```

`invalidate` is also `dual`, so it pipes:

```ts
import { Effect, Pool } from "effect"

declare const pool: Pool.Pool<Connection>
declare const conn: Connection

// data-first
Pool.invalidate(pool, conn)
// data-last (pipeable)
pool.pipe(Pool.invalidate(conn))
```

### isPool

Type guard that narrows an unknown value to `Pool<unknown, unknown>`.

```ts
import { Effect, Pool } from "effect"

Effect.gen(function*() {
  const pool = yield* Pool.make({ acquire: acquireConnection, size: 1 })
  console.log(Pool.isPool(pool)) // => true
  console.log(Pool.isPool({})) // => false
})
```

## Types

The pool's value, configuration, and strategy interfaces. Most code only ever
touches the `Pool` value via `get`/`invalidate`; the rest are exposed for
inspection and for writing a custom `Strategy`.

### Pool

The pool value: `Pool<A, E>`, where `A` is the item type and `E` is the error a
[`get`](#get) may fail with. It exposes its normalized [`Config`](#config) and
runtime [`State`](#state), and is `Pipeable`.

```ts
import type { Pool } from "effect"

declare const pool: Pool.Pool<Connection>

pool.config.maxSize // number
pool.state.items.size // current item count
```

### Config

The read-only, normalized configuration backing a pool: the scoped `acquire`
effect, `concurrency` (permits per item), `minSize`/`maxSize`, the resizing
[`strategy`](#strategy), and `targetUtilization` (clamped into `[0.1, 1]`).

```ts
import type { Pool } from "effect"

declare const pool: Pool.Pool<Connection>
const config: Pool.Config<Connection, never> = pool.config

config.minSize // e.g. 2
config.maxSize // e.g. 10
config.concurrency // permits per item
config.targetUtilization // 0.1 .. 1
```

### State

Mutable runtime state of a pool — its scope, the `items`/`available`/
`invalidated` item sets, the bounding semaphores, the availability `Latch`, the
`waiters` count, and the `isShuttingDown` flag. Read it for diagnostics; prefer
the high-level operations for control.

```ts
import type { Pool } from "effect"

declare const pool: Pool.Pool<Connection>
const state: Pool.State<Connection, never> = pool.state

state.items.size // total acquired items
state.available.size // items free to hand out
state.invalidated.size // items marked for removal
state.waiters // fibers currently waiting for an item
state.isShuttingDown // true once the pool scope is closing
```

### PoolItem

The internal record for one managed value: its acquisition `exit`, a
`finalizer`, the current `refCount`, and a `disableReclaim` flag set when the
item is invalidated. You receive `PoolItem`s when implementing a custom
[`Strategy`](#strategy).

```ts
import type { Pool } from "effect"

declare const item: Pool.PoolItem<Connection, never>

item.exit._tag // "Success" | "Failure"
item.refCount // how many fibers currently hold it
item.disableReclaim // true when invalidated
```

### Strategy

The lifecycle contract a pool uses for background resizing and reclamation, with
three callbacks:

- `run(pool)` — long-running background work (e.g. a TTL sweep loop), forked by
  the pool.
- `onAcquire(item)` — invoked each time an item is allocated.
- `reclaim(pool)` — returns a reusable `PoolItem` to recycle instead of
  allocating a fresh one, or `undefined`.

```ts
import { Effect, Pool } from "effect"

// Minimal strategy: no background work, no reclamation.
const strategy: Pool.Strategy<Connection, never> = {
  run: (_pool) => Effect.void,
  onAcquire: (_item) => Effect.void,
  reclaim: (_pool) => Effect.succeed(undefined)
}

const pool = Pool.makeWithStrategy({
  acquire: acquireConnection,
  min: 1,
  max: 4,
  strategy
})
```

## See also

- [Scope and finalizers](https://effect.plants.sh/resource-management/scope-and-finalizers/) — the
  lifetime model that governs both the pool and each checkout.
- [Acquire and release](https://effect.plants.sh/resource-management/acquire-release/) — how to write
  the scoped `acquire` effect a pool needs.
- [Semaphore and Latch](https://effect.plants.sh/concurrency/semaphore-and-latch/) — the concurrency
  primitives a pool is built from.
- [Resource](https://effect.plants.sh/caching/resource/) — for caching a *single* refreshable value
  rather than pooling many interchangeable ones.