# Scope and finalizers

A `Scope` is the lifetime of one or more resources. While a scope is **open**
you can attach **finalizers** to it; when the scope is **closed**, those
finalizers run. A finalizer is just an `Effect` that performs cleanup. This is
the foundation every other resource-management API in Effect is built on.

The high-level way to register a finalizer is `Effect.addFinalizer`. It adds the
finalizer to the *current* scope and gives you the [`Exit`](https://effect.plants.sh/data-types/) value
describing how the scope closed, so cleanup can react to success vs. failure.

```ts
import { Effect, Exit, Console } from "effect"

const program = Effect.gen(function*() {
  // Register two finalizers. Each receives the `Exit` the scope closed with.
  yield* Effect.addFinalizer((exit) =>
    Console.log(`finalizer 1 — exit was ${exit._tag}`)
  )
  yield* Effect.addFinalizer((exit) =>
    Console.log(`finalizer 2 — exit was ${exit._tag}`)
  )

  yield* Console.log("doing work...")
  return "result"
})

// `Effect.scoped` opens a scope, runs `program`, then closes the scope —
// running the finalizers. It also discharges the `Scope` requirement.
Effect.runPromise(Effect.scoped(program))
/*
Output:
doing work...
finalizer 2 — exit was Success   <-- finalizers run in reverse order
finalizer 1 — exit was Success
*/
```

Two things to internalize from this example:

- `program` has type `Effect<string, never, Scope>`. The `Scope` in the
  requirements channel means "I register finalizers and need a scope to run them
  in." `Effect.scoped` provides that scope and removes the requirement.
- Finalizers run in **reverse** order of registration, like unwinding a stack.
**Why reverse order?:** Resources are often layered: you open a connection, then open a transaction on
  top of it. Releasing in reverse order means the transaction is closed before
  the connection it depends on — the correct teardown sequence.

## Finalizers run on every outcome

The point of a finalizer is that it always runs once registered — on success,
on failure, and on interruption. Here the effect fails, and the finalizer still
fires and can observe the failure through the `Exit`.

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

const program = Effect.gen(function*() {
  yield* Effect.addFinalizer((exit) =>
    Console.log(`cleaning up — exit was ${exit._tag}`)
  )
  // Fail partway through.
  return yield* Effect.fail("boom")
})

Effect.runPromiseExit(Effect.scoped(program)).then(console.log)
/*
Output:
cleaning up — exit was Failure
{ _id: 'Exit', _tag: 'Failure', cause: { ... 'boom' ... } }
*/
```

## `ensuring`, `onExit`, and `onError`

`Effect.addFinalizer` targets the surrounding scope. When you only want cleanup
attached to a *single* effect — no scope in the type — reach for these
combinators instead. They mirror `try`/`finally`/`catch`:

- **`Effect.ensuring`** runs a finalizer whether the effect succeeds, fails, or
  is interrupted. It does not see the result. This is the closest analogue to a
  `finally` block.
- **`Effect.onExit`** runs cleanup and receives the full `Exit`, so it can
  branch on success vs. failure vs. interruption. The cleanup is uninterruptible.
- **`Effect.onError`** runs cleanup only when the effect fails (including by
  interruption), receiving the `Cause`.

```ts
import { Effect, Exit, Cause, Console } from "effect"

const work = Console.log("working...").pipe(Effect.as(42))

const program = work.pipe(
  // Always runs — good for releasing a lock or logging completion.
  Effect.ensuring(Console.log("ensuring: always runs")),
  // Sees the outcome — branch on how the effect ended.
  Effect.onExit((exit) =>
    Console.log(
      Exit.isSuccess(exit)
        ? `onExit: succeeded with ${exit.value}`
        : "onExit: did not succeed"
    )
  ),
  // Only runs on failure — receives the Cause for diagnostics.
  Effect.onError((cause) => Console.log(`onError: ${Cause.pretty(cause)}`))
)

Effect.runPromise(program)
/*
Output:
working...
onExit: succeeded with 42
ensuring: always runs
*/
```
**Finalizers vs. resources:** `ensuring` and `addFinalizer` are powerful but low-level. For acquiring and
  releasing an actual resource, prefer the
  [`acquireRelease` family](https://effect.plants.sh/resource-management/acquire-release/): it makes
  acquisition uninterruptible so a half-acquired resource can never leak.

## Nested lifetimes

Scopes compose. `Effect.scoped` always opens a *fresh* scope around the effect
it wraps, so you can carve out a shorter lifetime in the middle of a longer one.
Anything acquired inside the inner scope is released as soon as that inner block
finishes, while resources acquired in the outer scope live until the outer block
ends.

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

const resource = (name: string) =>
  Effect.acquireRelease(
    Console.log(`acquire ${name}`).pipe(Effect.as(name)),
    () => Console.log(`release ${name}`)
  )

const program = Effect.gen(function*() {
  // `outer` lives for the whole `program`.
  yield* resource("outer")

  // `inner` lives only for this nested scoped block — it is released
  // immediately when the block completes, before "after inner".
  yield* Effect.scoped(
    Effect.gen(function*() {
      yield* resource("inner")
      yield* Console.log("using inner")
    })
  )

  yield* Console.log("after inner")
})

Effect.runPromise(Effect.scoped(program))
/*
Output:
acquire outer
acquire inner
using inner
release inner      <-- inner scope closes here
after inner
release outer      <-- outer scope closes when program ends
*/
```

## Consuming a scoped resource

A value of type `Effect<A, E, Scope>` is *scoped*: it has acquired something and
registered a finalizer, and it cannot run until something provides a `Scope`.
`Effect.scoped` is how you discharge that requirement at the boundary of a
workflow — it opens a fresh scope, runs the effect, and closes the scope (with
the effect's own `Exit`) the instant the workflow finishes.

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

// `db` is scoped: its type is Effect<Db, never, Scope>.
const db = Effect.acquireRelease(
  Console.log("open connection").pipe(Effect.as({ query: () => "rows" })),
  () => Console.log("close connection")
)

const useDb = Effect.gen(function*() {
  const conn = yield* db
  yield* Console.log(`query returned: ${conn.query()}`)
})
// useDb : Effect<void, never, Scope>

// `Effect.scoped` provides the scope and removes it from the requirements,
// closing the connection as soon as `useDb` completes.
Effect.runPromise(Effect.scoped(useDb))
/*
Output:
open connection
query returned: rows
close connection
*/
```

When the resource should outlive a single workflow — for example, a connection
pool shared by an entire service — provide the scope at the application
boundary instead. [Layers](https://effect.plants.sh/services-and-layers/) hold scoped resources for the
lifetime of the layer, which is the usual real-world pattern.

## Working with a scope directly

For most code you never touch a `Scope` value — `Effect.scoped` and Layers
manage it. When you do need explicit control (for example, handing the same
scope to several independent tasks), `Effect.scopedWith` gives you the scope and
lets you register finalizers on it with `Scope.addFinalizer`.

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

const program = Effect.scopedWith((scope) =>
  Effect.gen(function*() {
    // Attach finalizers to the explicit scope.
    yield* Scope.addFinalizer(scope, Console.log("release A"))
    yield* Scope.addFinalizer(scope, Console.log("release B"))
    yield* Console.log("work")
    // When `scopedWith` returns, it closes `scope`, running both finalizers
    // in reverse order: "release B", then "release A".
  })
)

Effect.runPromise(program)
/*
Output:
work
release B
release A
*/
```

## Effect-level reference

These `Effect.*` combinators are what application code reaches for daily. They
operate on the *current* scope (provided by `Effect.scoped` or a Layer) without
ever forcing you to handle a `Scope` value by hand.

### Effect.addFinalizer

Registers a finalizer on the current scope. The finalizer receives the `Exit`
the scope closed with, and always runs once registered.

```ts
import { Effect, Exit, Console } from "effect"

const program = Effect.gen(function*() {
  yield* Effect.addFinalizer((exit) =>
    Console.log(`closing — ${Exit.isSuccess(exit) ? "ok" : "error"}`)
  )
  return "done"
})

Effect.runPromise(Effect.scoped(program)).then(console.log)
// => closing — ok
// => done
```

### Effect.acquireRelease

Builds a scoped resource from an acquire effect and a release finalizer.
Acquisition is run uninterruptibly by default, and the release runs when the
surrounding scope closes (receiving the `Exit`). The result requires `Scope`.

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

const file = Effect.acquireRelease(
  Console.log("open file").pipe(Effect.as("handle")),
  (handle, exit) => Console.log(`close ${handle} (${exit._tag})`)
)
// file : Effect<string, never, Scope>

Effect.runPromise(Effect.scoped(file))
// => open file
// => close handle (Success)
```

Pass `{ interruptible: true }` as the third argument if acquisition should be
interruptible. See [acquire / release](https://effect.plants.sh/resource-management/acquire-release/)
for the full treatment.

### Effect.acquireUseRelease

Brackets acquire, use, and release in a single effect — the classic
`bracket`/`try-with-resources` shape. Unlike `acquireRelease`, it does **not**
add `Scope` to the requirements: the resource lives exactly for the `use`
callback.

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

const program = Effect.acquireUseRelease(
  // acquire
  Console.log("connect").pipe(Effect.as({ query: () => "rows" })),
  // use
  (db) => Console.log(`got: ${db.query()}`),
  // release — receives the resource and the Exit of `use`
  (_db, exit) => Console.log(`disconnect (${exit._tag})`)
)

Effect.runPromise(program)
// => connect
// => got: rows
// => disconnect (Success)
```

### Effect.scoped

Provides a fresh scope to an effect, runs it, then closes the scope with the
effect's `Exit`. This is the standard way to discharge a `Scope` requirement at
the edge of a workflow.

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

const scopedWork = Effect.gen(function*() {
  yield* Effect.addFinalizer(() => Console.log("cleanup"))
  return 42
})
// scopedWork : Effect<number, never, Scope>

Effect.runPromise(Effect.scoped(scopedWork)).then(console.log)
// => cleanup
// => 42
```

### Effect.scopedWith

Like `Effect.scoped`, but passes the freshly created `Scope` to a callback so
you can register finalizers on it directly. The scope is closed when the
callback's effect completes.

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

const program = Effect.scopedWith((scope) =>
  Scope.addFinalizer(scope, Console.log("released")).pipe(
    Effect.andThen(Console.log("running"))
  )
)

Effect.runPromise(program)
// => running
// => released
```

### Effect.scope

Accesses the current `Scope` from context. It has type
`Effect<Scope, never, Scope>` — it both requires and yields the active scope —
so use it inside a scoped workflow when you need the scope value itself.

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

const program = Effect.gen(function*() {
  // Grab the active scope and register a finalizer on it.
  const scope = yield* Effect.scope
  yield* Scope.addFinalizer(scope, Console.log("done"))
  yield* Console.log("work")
})

Effect.runPromise(Effect.scoped(program))
// => work
// => done
```

### Effect.ensuring

Attaches a finalizer to a single effect. It runs on success, failure, or
interruption but does not observe the result. No `Scope` is added to the type.

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

const program = Console.log("task").pipe(
  Effect.ensuring(Console.log("always"))
)

Effect.runPromise(program)
// => task
// => always
```

### Effect.onExit

Runs cleanup that receives the full `Exit` of the effect. The cleanup is
uninterruptible, so it is safe for teardown that must complete.

```ts
import { Effect, Exit, Console } from "effect"

const program = Effect.succeed(1).pipe(
  Effect.onExit((exit) => Console.log(`exit: ${exit._tag}`))
)

Effect.runPromise(program)
// => exit: Success
```

### Effect.onError

Runs cleanup only when the effect fails (including by interruption), passing the
`Cause`. On success the cleanup is skipped.

```ts
import { Effect, Cause, Console } from "effect"

const program = Effect.fail("boom").pipe(
  Effect.onError((cause) => Console.log(`failed: ${Cause.pretty(cause)}`))
)

Effect.runPromiseExit(program)
// => failed: Error: boom
```

## Scope module reference

The `Scope` module exposes the primitives the combinators above are built from.
You reach for it when you need explicit control over a lifetime boundary — for
example wiring infrastructure, sharing one scope across several tasks, or
building a [resource pool](https://effect.plants.sh/resource-management/pool/). A `Scope` has a
`strategy` (`"sequential"` finalizers run in reverse registration order;
`"parallel"` run concurrently) and a `state` that is `Empty`, `Open`, or
`Closed`.

### Scope (interface)

A `Scope` is a mutable lifetime boundary: while `state._tag === "Open"` it
accepts finalizers, and closing it runs them. It exposes `strategy` and
`state`.

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

const program = Effect.gen(function*() {
  const scope = yield* Scope.make("sequential")
  console.log(scope.strategy) // => "sequential"
  console.log(scope.state._tag) // => "Empty"

  yield* Scope.addFinalizer(scope, Console.log("cleanup"))
  console.log(scope.state._tag) // => "Open"
})
```

### Closeable (interface)

A `Closeable` extends `Scope` with the ability to be closed via
[`Scope.close`](#scopeclose) or [`Scope.use`](#scopeuse). The scope constructors
return `Closeable` scopes; the `Scope` service tag exposes only the narrower
`Scope` interface.

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

const program = Effect.gen(function*() {
  const scope: Scope.Closeable = yield* Scope.make()
  yield* Scope.close(scope, Exit.void) // only Closeable scopes can be closed
})
```

### Scope.Scope

The service tag (and reference) for the active scope in context. Yield it inside
a scoped workflow to obtain the current `Scope`.

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

const program = Effect.gen(function*() {
  const scope = yield* Scope.Scope
  yield* Scope.addFinalizer(scope, Console.log("cleanup"))
})

// `Effect.scoped` provides the Scope service.
Effect.runPromise(Effect.scoped(program))
// => cleanup
```

### Scope.make

Creates a fresh `Closeable` scope as an effect, with an optional finalizer
strategy (`"sequential"` by default).

```ts
import { Effect, Exit, Scope, Console } from "effect"

const program = Effect.gen(function*() {
  const scope = yield* Scope.make("sequential")
  yield* Scope.addFinalizer(scope, Console.log("1"))
  yield* Scope.addFinalizer(scope, Console.log("2"))
  yield* Scope.close(scope, Exit.void) // sequential => reverse order
})

Effect.runPromise(program)
// => 2
// => 1
```

### Scope.makeUnsafe

Creates a `Closeable` scope synchronously, outside the `Effect` runtime. Use
only when integrating with non-Effect code that needs a scope immediately.

```ts
import { Scope } from "effect"

const scope = Scope.makeUnsafe("parallel")
console.log(scope.strategy) // => "parallel"
console.log(scope.state._tag) // => "Empty"
```

### Scope.provide

Provides a concrete `Scope` to an effect that requires one, removing `Scope`
from its requirements. Unlike [`use`](#scopeuse), it does **not** close the
scope afterwards — you remain responsible for closing it.

```ts
import { Effect, Exit, Scope, Console } from "effect"

const needsScope = Effect.gen(function*() {
  const scope = yield* Scope.Scope
  yield* Scope.addFinalizer(scope, Console.log("released"))
  yield* Console.log("working")
})

const program = Effect.gen(function*() {
  const scope = yield* Scope.make()
  yield* Scope.provide(needsScope, scope) // requirement discharged, not closed
  yield* Console.log("before close")
  yield* Scope.close(scope, Exit.void) // we close it ourselves
})

Effect.runPromise(program)
// => working
// => before close
// => released
```

### Scope.addFinalizer

Registers a finalizer on a scope that ignores the closing `Exit`. If the scope
is already closed, the finalizer runs immediately.

```ts
import { Effect, Exit, Scope, Console } from "effect"

const program = Effect.gen(function*() {
  const scope = yield* Scope.make()
  yield* Scope.addFinalizer(scope, Console.log("cleanup"))
  yield* Scope.close(scope, Exit.void)
})

Effect.runPromise(program)
// => cleanup
```

### Scope.addFinalizerExit

Registers a finalizer that receives the `Exit` the scope was closed with, so it
can branch on success vs. failure. If the scope is already closed, it runs
immediately with the stored `Exit`.

```ts
import { Effect, Exit, Scope, Console } from "effect"

const program = Effect.gen(function*() {
  const scope = yield* Scope.make()
  yield* Scope.addFinalizerExit(scope, (exit) =>
    Console.log(`closing with ${exit._tag}`)
  )
  yield* Scope.close(scope, Exit.fail("boom"))
})

Effect.runPromise(program)
// => closing with Failure
```

### Scope.fork

Creates a `Closeable` child scope registered with a parent. Closing the parent
closes the child with the same `Exit`; closing the child first detaches it from
the parent. The child takes its own finalizer strategy.

```ts
import { Effect, Exit, Scope, Console } from "effect"

const program = Effect.gen(function*() {
  const parent = yield* Scope.make("sequential")
  yield* Scope.addFinalizer(parent, Console.log("parent cleanup"))

  // Child lifetime is bound to the parent.
  const child = yield* Scope.fork(parent, "parallel")
  yield* Scope.addFinalizer(child, Console.log("child cleanup"))

  // Closing the parent also closes the still-open child.
  yield* Scope.close(parent, Exit.void)
})

Effect.runPromise(program)
// => child cleanup
// => parent cleanup
```

### Scope.forkUnsafe

The synchronous variant of [`fork`](#scopefork): creates a child scope without
wrapping it in an `Effect`. Use only in low-level, non-Effect integration code.

```ts
import { Scope } from "effect"

const parent = Scope.makeUnsafe("sequential")
const child = Scope.forkUnsafe(parent, "parallel")

console.log(child.strategy) // => "parallel"
console.log(child.state._tag) // => "Empty"
```

### Scope.close

Closes a scope and runs its finalizers with the supplied `Exit`. This is itself
an `Effect` — finalizers run only when it is executed.

```ts
import { Effect, Exit, Scope, Console } from "effect"

const program = Effect.gen(function*() {
  const scope = yield* Scope.make("sequential")
  yield* Scope.addFinalizer(scope, Console.log("a"))
  yield* Scope.addFinalizer(scope, Console.log("b"))
  yield* Scope.close(scope, Exit.succeed("ok"))
})

Effect.runPromise(program)
// => b
// => a
```

### Scope.closeUnsafe

Transitions a scope to `Closed` synchronously and returns an `Effect` that runs
the finalizers (or `undefined` if there is nothing to run). For low-level
machinery only — ignoring the returned effect skips the finalizers.

```ts
import { Effect, Exit, Scope, Console } from "effect"

const scope = Scope.makeUnsafe()
Effect.runSync(Scope.addFinalizer(scope, Console.log("cleanup")))

// Returns the finalizer effect; you must run it.
const finalize = Scope.closeUnsafe(scope, Exit.void)
if (finalize) Effect.runSync(finalize)
// => cleanup
```

### Scope.use

Runs an effect with a `Closeable` scope in its context and closes that scope
with the effect's `Exit` when it exits. Use it when you already hold a scope and
want automatic closing (whereas [`provide`](#scopeprovide) leaves it open).

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

const work = Effect.gen(function*() {
  const scope = yield* Scope.Scope
  yield* Scope.addFinalizer(scope, Console.log("released"))
  yield* Console.log("working")
})

const program = Effect.gen(function*() {
  const scope = yield* Scope.make()
  yield* Scope.use(work, scope) // runs `work`, then closes `scope`
})

Effect.runPromise(program)
// => working
// => released
```
**State namespace:** `Scope.State` describes the three concrete states a scope can be in: `Empty`
  (open, no finalizers yet), `Open` (open with a `finalizers` map), and `Closed`
  (carrying the `exit` it was closed with). You rarely inspect these directly,
  but they are useful when debugging a custom scope.

Now that you understand scopes and finalizers, the next page covers the
ergonomic, leak-safe way to define resources:
[acquire / release](https://effect.plants.sh/resource-management/acquire-release/). To pool and reuse
expensive scoped resources, see [Pool](https://effect.plants.sh/resource-management/pool/).