Skip to content

Runtime

An Effect is just a description of a computation — building one runs nothing. Nothing happens until you hand that description to a runtime, which spawns a fiber to actually carry it out. This is the same principle as a lazy () => Promise: you compose effects freely, then execute the whole program in one place.

The golden rule is to run effects at the edge of your application. Keep the vast majority of your logic as composed effects, and call a run* function (or a platform runMain) once, as close to the “outside world” as possible — your main.ts, an HTTP handler, a test. Pushing execution to the edge keeps your code composable, testable, and free of half-run side effects scattered through the middle of your program.

import { Effect } from "effect"
// A description. Nothing has happened yet.
const program = Effect.gen(function*() {
yield* Effect.log("Hello from a fiber")
return 42
})
// Execution happens here, once, at the edge.
Effect.runPromise(program).then((n) => console.log(n))
// Output:
// ... level=INFO message="Hello from a fiber"
// 42
  • Running effects — the run* family (runSync, runPromise, runFork, and their Exit variants), and how to choose between them.
  • Running as an entrypointNodeRuntime.runMain / BunRuntime.runMain, the production-grade way to make an Effect your process root, with signal handling and graceful shutdown.
  • Launching applicationsLayer.launch, for long-running apps (servers, workers) expressed entirely as layers.
You want…Use
A process entrypoint (CLI, server, worker)NodeRuntime.runMain / BunRuntime.runMain
A long-running app built from layersLayer.launch + runMain
A Promise for interopEffect.runPromise
A background fiber to observe or interruptEffect.runFork
An immediate, purely synchronous resultEffect.runSync

For most applications you will reach for runMain rather than calling the low-level run* functions directly — it adds error reporting and graceful shutdown on top of runFork. The Effect.run* functions are the right tool when embedding Effect inside an existing host (a React event handler, an Express route, an existing async function).

Effects must have their requirements satisfied before they can run. A run*/runMain function only accepts an Effect<A, E, never> — the R channel must be empty. You eliminate requirements by providing services and layers before reaching the edge.