FIRST PRINCIPLES OF EFFECT

Master Effect<A, E, R> from first principles.

A Promise<A> tells you what succeeds, conceals how it fails, and hides what it needs to run. Learn how Effect<A, E, R> turns async TypeScript into pure, composable values with explicit outputs (A), typed failure modes (E), and declared dependencies (R).

A (success value) • E (typed errors) • R (required context)

A production operation has more than a return value.

Follow one real-world operation as each hidden assumption becomes an explicit policy.

01

Work can start too early

A Promise executes before the caller can attach a timeout, retry schedule, or test double.

02

Steps depend on results

The next step must receive the value produced by the previous step without using hardcoded placeholders.

03

Failures carry meaning

An empty result, an invalid query, and an external provider outage require fundamentally different responses.

04

Dependencies need clear ownership

Domain logic should not construct database clients, locks, or HTTP providers internally.

05

Work needs a resource budget

Retries, cleanup guarantees, and concurrency limits decide how the system behaves under load.

The Anatomy of Effect<A, E, R>

An Effect is an immutable value describing a computation: what it produces, how it can fail, and what capabilities it requires.

A

The Success Value (A)

A is the type produced when the operation succeeds, such as parsed records or a validated entity.

E

The Error Channel (E)

E names expected domain failures, forcing callers to handle them without falling back to catch(e: unknown).

R

The Requirements Channel (R)

R defines environmental capabilities and services the operation needs. The execution boundary must provide them.

Watch an operation become resilient.

Step through each operational decision before adding the next abstraction.

Interactive operation workbench • five decisions

One operation, five decisionsStage 0

The hidden policies of Promise
Stage 0: a plain async operation

A Promise only tells you when work finishes, not how the operation behaves.

A production operation has far more decisions than its return value. The plain async version hides them in callers: 1. The provider call starts immediately when the function runs. 2. A provider outage becomes an untyped rejection. 3. The function hardcodes its production dependencies. 4. An acquired lock or temporary resource can leak if the operation fails mid-flight. Before reaching for an abstraction, make those decisions visible. Then choose a model that can carry them together.
First-principles consequence
The problem is not async syntax. The problem is that Promise has no place to carry failure types or resource policies.
workspace-sync.ts
Baseline problem • hidden policy
type SyncResult = { readonly imported: number }

declare function fetchWorkspaceIssues(): Promise<readonly string[]>
declare function saveIssues(issues: readonly string[]): Promise<SyncResult>

async function syncWorkspace(): Promise<SyncResult> {
  const issues = await fetchWorkspaceIssues()
  return saveIssues(issues)
}

// Where do timeout, retry, cleanup, and a test provider live?
const result = await syncWorkspace()

Break the contract before you trust it.

Each lab starts with a broken policy or failing contract. Inspect the test assertion, repair the implementation, and verify it.

LAB 01 / MODULE 01

Defer execution to the boundary

Keep program assembly side-effect free until Effect.runPromise executes it.

pnpm exec tsx src/exercises/exercise1/exercise.test.ts starter
LAB 02 / MODULE 02

Sequence dependent operations

Feed the result of the first Effect directly into the next operation using Effect.gen.

pnpm exec tsx src/exercises/exercise2/exercise.test.ts starter
LAB 03 / MODULE 03

Keep search failures distinct

Preserve provider outages and invalid queries in the E channel instead of masking them as empty arrays.

pnpm exec tsx src/exercises/exercise3/exercise.test.ts starter
LAB 04 / MODULE 04

Provide required services

Supply the declared Context.Tag at the execution boundary using Effect.provideService.

pnpm exec tsx src/exercises/exercise4/exercise.test.ts starter
LAB 05 / MODULE 05

Scoped resource cleanup

Register finalizers that guarantee releasing locks and flushing buffers on both success and failure.

pnpm exec tsx src/exercises/exercise5/exercise.test.ts starter
LAB 06 / MODULE 06

Selective failure retries

Retry temporary 503s with exponential backoff while failing immediately on 401 Unauthorized.

pnpm exec tsx src/exercises/exercise6/exercise.test.ts starter
LAB 07 / MODULE 07

Bound parallel execution

Process a full batch while bounding active concurrent fibers to prevent rate-limit exhaustion.

pnpm exec tsx src/exercises/exercise7/exercise.test.ts starter

Every claim has a runnable check.

Run the examples, strict typechecks, and test labs locally. The browser explains the mental model. The terminal verifies it.

pnpm checkpnpm testpnpm build