Skip to content

Concurrency

Effect is a concurrent framework powered by fibers — lightweight, virtual threads scheduled cooperatively by the Effect runtime. Because JavaScript is single-threaded, fibers don’t give you parallel CPU execution, but they do give you something more valuable: thousands of independent, cancellable units of work that interleave safely on a single thread, with first-class interruption and resource cleanup.

The defining property of concurrency in Effect is that it is structured. A fiber you fork is a child of the fiber that forked it. When the parent finishes, is interrupted, or fails, its children are interrupted too and their finalizers run. You never leak a background task by accident, and you never have to manually track every fiber you started — the runtime does it for you, the same way a try/finally block scopes a resource.

import { Effect, Fiber } from "effect"
const program = Effect.gen(function*() {
// Fork a child fiber. It is automatically supervised by this fiber:
// if `program` finishes or is interrupted, the child is interrupted too.
const fiber = yield* Effect.forkChild(
Effect.sleep("1 second").pipe(Effect.as("done"))
)
// Wait for the child's result, re-raising its failure into this fiber.
const result = yield* Fiber.join(fiber)
yield* Effect.log(result)
})
Effect.runFork(program)
  • Fibers — fork an effect into a child fiber, then join, await, or interrupt it. Covers the supervised / scoped / detached fork strategies that determine a fiber’s lifetime.
  • Concurrency options — the concurrency option on Effect.forEach, Effect.all, and friends, for running a collection of effects with a bounded or unbounded degree of parallelism.
  • Interruption — how Effect cancels work safely: the asynchronous interruption model, uninterruptible regions, and onInterrupt cleanup.
  • Racing and timeoutsrace, raceAll, raceFirst, and timeout for “first one wins” semantics.
  • Semaphore and Latch — limit how many fibers run at once with a Semaphore; gate fibers until a condition is met with a Latch.
  • Fiber collections — supervise many fibers with FiberHandle, FiberMap, and FiberSet.
  • Queue and PubSubQueue for distributing work among consumers; PubSub for fanning a message out to every subscriber.

You rarely create the runtime that runs fibers yourself. An effect runs inside a fiber the moment you hand it to a runner like Effect.runFork, Effect.runPromise, or a platform entrypoint such as NodeRuntime.runMain (see Runtime). From there, concurrency comes from a small set of tools:

  • Forking an effect with Effect.forkChild (and its variants) starts a new fiber explicitly.
  • Concurrency options on combinators like Effect.forEach fork a fiber per element behind the scenes.
  • Racing and timeouts fork competitors and interrupt the losers.

Everything else in this section — semaphores, latches, queues, pubsubs, and the fiber collections — exists to coordinate those fibers safely.