A hands-on guide to Effect

The Comprehensive Guide to Effect

Effect is a TypeScript library for building complex applications that stay readable and reliable as they grow. This guide covers it twice — first what it is and why it exists, then by rebuilding its engine from scratch, piece by piece.

What this is

Contrary to most sources, this guide focuses less on how to use Effect and more on why it exists and how it works under the hood — down to the loop that runs your code.

It comes in two parts. Ideally read them in order.

The one idea

A normal function's type tells you what it returns when everything goes right. It stays quiet about two things: how it can fail, and what it needs to run. Those silences turn into surprises — an exception you didn't expect, a dependency you forgot to wire up.

Effect puts all three in one type:

Effect<Success, Error, Requirements> Success the value you get if it works Error the ways it can fail, as values Requirements what it needs from the outside
Three slots: the value, the ways it can fail, and what it needs — all in the type.

The smallest possible before/after — a function that can fail:

Plain — the type hides the failure
const divide = (a: number, b: number): number => {
  if (b === 0) throw new Error("Cannot divide by zero")
  return a / b
}
// type says `number`. it does not say "or throws".
// you only find the failure by reading the body.
Effect — the failure is in the type
import { Effect } from "effect"

const divide = (a: number, b: number): Effect.Effect<number, Error> =>
  b === 0
    ? Effect.fail(new Error("Cannot divide by zero"))
    : Effect.succeed(a / b)
// the caller sees it without reading the body,
// and the compiler makes sure they deal with it.