In v3, each call to Effect.provide created its own memoization scope. Layers
were memoized / deduplicated within a single Effect.provide call, but would
not be shared across separate calls — so two Effect.provide calls with
overlapping layers would silently build those layers twice.
In v4, the underlying MemoMap data structure which facilitates memoization of
Layers is shared between Effect.provide calls (unless explicitly disabled
via the { local: true } option). Thus, layers are automatically memoized /
deduplicated across Effect.provide calls.
import { Console, Effect, Layer, ServiceMap } from "effect"
const MyService = ServiceMap.Service<{ readonly value: string }>("MyService")
const MyServiceLayer = Layer.effect(
MyService,
Effect.gen(function*() {
yield* Console.log("Building MyService")
return { value: "hello" }
})
)
const program = Effect.gen(function*() {
const a = yield* MyService
return a.value
})
// Same layer provided twice in separate provide calls
const main = program.pipe(
Effect.provide(MyServiceLayer),
Effect.provide(MyServiceLayer)
)
// Effect v3: "Building MyService" is logged TWICE
// Effect v4: "Building MyService" is logged ONCE
Effect.runPromise(main)Even though v4 memoizes across provide calls, composing layers before
providing is still the recommended pattern. Layer composition makes your
dependency graph explicit and lets you see the full structure in one place:
// Preferred — provide once
const main = program.pipe(Effect.provide(MyServiceLayer))The auto-memoization feature is a safety net to avoid the footguns associated
with multiple Effect.provide calls present in v3. It is NOT a substitute
for proper layer composition.
There are cases where you want a layer to be built fresh — for example, test isolation or creating independent resource pools. v4 provides two mechanisms:
Wraps a layer so it always builds with a fresh memo map, bypassing the shared cache. This existed in v3 as well.
import { Effect, Layer } from "effect"
const main = program.pipe(
Effect.provide(MyServiceLayer),
Effect.provide(Layer.fresh(MyServiceLayer))
)
// "Building MyService" is logged TWICE — fresh bypasses the shared cacheNew in v4. Builds the provided layer with a local memo map instead of the
fiber's shared one. The layer and all its sublayers are built from scratch and
are not shared with other provide calls.
import { Effect } from "effect"
const main = program.pipe(
Effect.provide(MyServiceLayer),
Effect.provide(MyServiceLayer, { local: true })
)
// "Building MyService" is logged TWICE — local creates its own memo mapUse local: true when you need an entire layer subtree to be isolated — for
example, when providing layers in a test harness where each test should get
independent resources.