Skip to content

What Is Effect?

Effect is a TypeScript library for building robust, concurrent, and maintainable applications. At its core is a single data type, Effect, that describes a program as a value — a lazy blueprint of work to be done — instead of running it immediately. Because the program is a value, Effect can give you things that plain functions and Promises cannot: errors tracked in the type system, dependency injection that the compiler enforces, automatic resource cleanup, structured concurrency, retries, timeouts, tracing, and more — all from one consistent toolkit.

Here is the idea in one example. Notice that the return type tells you exactly what this program does, including how it can fail and what it needs to run.

import { Console, Effect } from "effect"
// `Effect.gen` lets you write effectful code in a familiar imperative style.
// `yield*` runs an effect and unwraps its success value — like `await`, but for
// Effect values.
const program = Effect.gen(function* () {
yield* Console.log("Fetching the current user...")
// Most of your logic is just sequencing effects with `yield*`.
const name = yield* Effect.succeed("Ada")
yield* Console.log(`Hello, ${name}!`)
return name
})
// ┌─── Effect<string, never, never>
// ▼
program

The type Effect<string, never, never> is the heart of Effect: it captures the success value, the possible errors, and the required dependencies of a computation, all at once.

Every Effect carries three type parameters:

┌─── A: the value it produces on success
│ ┌─── E: the way it can fail (a typed error)
│ │ ┌─── R: the services it needs to run
▼ ▼ ▼
Effect<Success, Error, Requirements>
  • A (success) — the value you get when the program completes successfully.
  • E (error) — the typed, expected failures the program can produce. Unlike thrown exceptions, these errors are visible in the type and the compiler forces you to handle them. (Unexpected failures — defects — are tracked separately, in the Cause.)
  • R (requirements) — the services and context the program depends on, such as a database, an HTTP client, or configuration. The compiler will not let you run an Effect until every requirement in R has been provided.

An Effect is lazy: building one performs no work. Nothing happens until you hand it to a runtime via Effect.runPromise, Effect.runSync, or a platform runMain. This separation between describing a program and running it is what makes Effect composable.

In ordinary TypeScript, a function’s signature tells you what it returns on success — but says nothing about how it can fail or what it secretly depends on:

// Looks total, but can throw and reads from a hidden global. The compiler
// gives you no warning about either.
const divide = (a: number, b: number): number => {
if (b === 0) throw new Error("Cannot divide by zero")
return a / b
}

Effect moves errors and dependencies into the type system, so the compiler can help you reason about the whole behaviour of your program, not just the happy path:

import { Effect } from "effect"
// The signature now documents the failure mode. Callers must handle it.
const divide = (a: number, b: number): Effect.Effect<number, Error> =>
b === 0
? Effect.fail(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)

This single shift unlocks a whole ecosystem of capabilities that compose cleanly, because they all speak the same Effect language:

Typed errors

Failures are values in the type system. The compiler forces you to handle them — no forgotten try/catch.

Dependency injection

Services and configuration are tracked in R and provided by Layers. Swap real implementations for mocks without touching business logic.

Structured concurrency

A fiber-based runtime gives you cheap concurrency, interruption, timeouts, and racing — with guaranteed cleanup.

One ecosystem

Retries, scheduling, streaming, caching, tracing, schema, HTTP, RPC and more — one consistent toolkit instead of a dozen dependencies.

This section gets you from zero to a running Effect program:

  • Why Effect? — the motivation in more depth: typed errors, dependency injection, structured concurrency, and a single ecosystem.
  • Installation — set up a TypeScript project with Bun, npm, or pnpm.
  • Quickstart — build a small but real Effect program end to end.
  • Importing Effect — how the package is laid out, and how to import core and unstable modules.

When you are ready to go deeper, head to Effect Essentials to learn how to create, transform, and run effects, then on to Error Management and Services & Layers.