# Why Effect?

Programming is hard, and most of the difficulty is not the happy path — it is
everything around it: errors, dependencies, concurrency, cancellation, retries,
and cleanup. In ordinary TypeScript these concerns live *outside* the type
system, so the compiler can't help you with them. Effect's central insight is to
bring them *into* the type system, where they become explicit, composable, and
checked. The result is code that is easier to reason about, refactor, and test.

Consider a realistic service that fetches a user. Notice how the signature
documents not just the result, but how the work can fail and what it depends on.

```ts
import { Context, Effect, Layer, Schema } from "effect"

// Errors are data: defined with Schema so they are typed, serializable, and
// carry structured fields rather than an opaque message string.
class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()(
  "UserNotFound",
  { id: Schema.Number }
) {}

class User extends Schema.Class<User>("User")({
  id: Schema.Number,
  name: Schema.String
}) {}

// A service is declared as a class. Its shape is just an interface of effects.
class Users extends Context.Service<Users, {
  readonly findById: (id: number) => Effect.Effect<User, UserNotFound>
}>()("app/Users") {
  // The `layer` describes how to build the live implementation.
  static layer = Layer.sync(Users, () => {
    const store = new Map<number, User>([[1, new User({ id: 1, name: "Ada" })]])

    return Users.of({
      // `Effect.fn` is the idiomatic way to write a function that returns an
      // Effect: it names the function for stack traces and tracing spans.
      findById: Effect.fn("Users.findById")(function* (id: number) {
        const user = store.get(id)
        if (user === undefined) {
          // Returning a failure is just another value in the flow.
          return yield* new UserNotFound({ id })
        }
        return user
      })
    })
  })
}

// Business logic depends on `Users` without knowing the implementation.
//
//        ┌─── the error is visible: UserNotFound
//        │              ┌─── the dependency is visible: Users
//        ▼              ▼
//   Effect<string, UserNotFound, Users>
const greet = Effect.fn("greet")(function* (id: number) {
  const users = yield* Users
  const user = yield* users.findById(id)
  return `Hello, ${user.name}!`
})
```

Every important fact about `greet` is in its type: it succeeds with a `string`,
it can fail with `UserNotFound`, and it needs a `Users` service to run. None of
this is documentation that can drift out of date — it is checked by the
compiler. Below are the four pillars that make this possible.

## Typed errors

In typical TypeScript, a function either returns a value or throws. The throw is
invisible to the type system, so it is easy to forget that a call can fail and
easy to forget to handle it. As a codebase grows from one such function to a
thousand, this becomes a real source of bugs.

Effect tracks errors in the `E` channel of `Effect<A, E, R>`. When you call
something that can fail, the failure shows up in your type, and the compiler
keeps it there until you handle it — with [`Effect.catch`](https://effect.plants.sh/error-management/),
`Effect.catchTag`, or by recovering with a fallback. Errors are ordinary
tagged values (defined with `Schema.TaggedErrorClass`), so you can match on them
exhaustively and even serialize them across a network boundary.
**Note:** Effect distinguishes **expected errors** (the `E` channel — things you model and
recover from) from **defects** (unexpected bugs, tracked in the `Cause`). This
mirrors how you actually reason about failures: a missing user is recoverable; a
null-pointer bug is not.

## Dependency injection

The `greet` function above asks for a `Users` service simply by `yield*`-ing it.
It never imports a concrete implementation, so the `R` channel records `Users`
as an unmet requirement. You satisfy that requirement by **providing a Layer** —
a recipe for constructing the service and its own dependencies:

```ts
import { Effect } from "effect"

// Providing `Users.layer` discharges the requirement: the resulting Effect has
// `R = never` and is ready to run.
const runnable = greet(1).pipe(Effect.provide(Users.layer))
```

Because dependencies are values, you can provide a real database-backed `Users`
in production and an in-memory one in tests — without changing a single line of
business logic. The compiler guarantees you never forget to provide something:
an unprovided service is a type error, not a runtime crash. See
[Services & Layers](https://effect.plants.sh/services-and-layers/) for the full story.

## Structured concurrency

Effect runs on a lightweight **fiber** runtime. Fibers are cheap, cooperatively
scheduled green threads — you can spawn thousands of them. Crucially, Effect's
concurrency is *structured*: child fibers are tied to their parent's lifetime, so
when a parent finishes, fails, or is interrupted, its children are cleaned up
automatically. There are no leaked tasks and no dangling work.

This gives you racing, timeouts, interruption, and bounded parallelism as
first-class, composable operations — and resource acquisition/release that is
guaranteed to run even under interruption. Explore this in
[Concurrency](https://effect.plants.sh/concurrency/) and [Resource Management](https://effect.plants.sh/resource-management/).

## One ecosystem

The biggest practical payoff is that everything speaks the same language. In a
typical project you might reach for separate libraries for retries, validation,
streaming, caching, tracing, and HTTP — each with its own API and its own way of
handling errors and async. Effect provides standardized, composable solutions
for all of them under one umbrella:

- [Scheduling](https://effect.plants.sh/scheduling/) and retries with composable policies
- [Schema](https://effect.plants.sh/schema/) for parsing, validation, and serialization
- [Streaming](https://effect.plants.sh/streaming/) for pull-based, backpressured data
- [Caching](https://effect.plants.sh/caching/) and [Batching](https://effect.plants.sh/batching/) for efficiency
- [Observability](https://effect.plants.sh/observability/) — tracing, logs, and metrics built in
- [HTTP Client](https://effect.plants.sh/http-client/), [HTTP API](https://effect.plants.sh/http-api/), [RPC](https://effect.plants.sh/rpc/),
  [SQL](https://effect.plants.sh/sql/), [CLI](https://effect.plants.sh/cli/), and [AI](https://effect.plants.sh/ai/) for building real systems

Because these tools all return `Effect` values, they compose with each other and
with your own code for free. You don't have to adopt all of it at once — start
with the pieces that solve your immediate problem, and reach for more as you go.

Next, get a project set up in [Installation](https://effect.plants.sh/introduction/installation/), then
build something real in the [Quickstart](https://effect.plants.sh/introduction/quickstart/).