Batching
Real programs spend a lot of time talking to slow, remote things: databases, HTTP APIs, caches. When a single logical operation fans out into dozens of individual lookups — one query per user, one fetch per id — the naive approach issues one round-trip per call. The network round-trips, not the CPU, become the bottleneck.
Effect’s batching system lets you describe what you need (a Request) separately from how it is fetched (a RequestResolver). When many requests are made — even across concurrent fibers — Effect collects them into a single batch, hands them to the resolver together, and automatically deduplicates identical requests. Your business logic keeps asking for one thing at a time; the runtime turns that into one efficient external call.
import { Effect } from "effect"
// `getUserById` looks like a simple per-id lookup...declare const getUserById: (id: number) => Effect.Effect<string>
// ...but running many of them concurrently produces a *single* batched// call to the underlying resolver, with duplicate ids (1 and 2) collapsed.const program = Effect.forEach([1, 2, 1, 3, 2], getUserById, { concurrency: "unbounded"})How it works
Section titled “How it works”- A Request is a typed description of one piece of data you want — its inputs, its success type, and its error type. Requests are plain values, so two identical requests are equal and can be deduplicated.
- A RequestResolver receives a non-empty batch of pending requests and completes each
one. This is the only place that touches the external system, so it is where you write a
bulk query (
WHERE id IN (...)) or a batched API call. Effect.request(req, resolver)ties them together. The runtime gathers all requests made within a short delay window (across concurrent fibers), passes the unique set to the resolver, and routes each result back to the fiber that asked for it.
In this section
Section titled “In this section”- Request & RequestResolver — define request types, write a batching resolver, deduplicate, add a delay window, tracing, and a cache.
Related
Section titled “Related”- Caching memoizes effects by input; batching collapses concurrent calls into
one round-trip. The two compose —
RequestResolver.withCacheadds caching on top of a resolver. - Concurrency — batching shines when requests are issued from many fibers
with
Effect.forEach(..., { concurrency }). - Observability — resolvers can wrap each batch in a span so you can see exactly how many requests collapsed into one call.