# Control Flow

JavaScript already has `if`, `for`, and `while`, and inside
[`Effect.gen`](https://effect.plants.sh/essentials/using-generators/) you should use them freely - they
are the simplest way to branch and loop. Effect adds a handful of combinators
for the cases where ordinary control flow does not fit a pipeline: running an
effect *conditionally*, *filtering* a value through a predicate, iterating an
effect *over a collection*, and looping a *single* effect (until a condition,
forever, or a fixed number of times).

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

// Inside a generator, plain `if` works as you'd expect. A function that
// returns an effect is written with `Effect.fn`.
const validateWeight = Effect.fn("validateWeight")(function*(weight: number) {
  if (weight >= 0) {
    return Option.some(weight)
  }
  yield* Effect.logWarning(`Rejecting negative weight: ${weight}`)
  return Option.none()
})
```

## Branching inside a generator

Most branching just uses native syntax. Return from the success channel, or fail
through the error channel - whichever models your domain:

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

const validateWeightOrFail = Effect.fn("validateWeightOrFail")(
  function*(weight: number) {
    if (weight < 0) {
      // Fail with a typed error.
      return yield* Effect.fail(`negative input: ${weight}` as const)
    }
    // Succeed with the valid value.
    return weight
  }
)
```

## `when` - run an effect conditionally

`Effect.when` runs an effect only when a **condition effect** evaluates to
`true`. The result is wrapped in an [`Option`](https://effect.plants.sh/data-types/): `Some` if the
effect ran, `None` if it was skipped. This is the pipeline-friendly counterpart
to an `if` statement.

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

// Roll a die, but only log it when a coin flip comes up heads.
//      ┌─── Effect<Option<number>>
//      ▼
const program = Random.nextInt.pipe(
  Effect.tap((n) => Effect.log(`rolled ${n}`)),
  // The second argument is the condition - itself an effect.
  Effect.when(Random.nextBoolean)
)

Effect.runFork(program)
```

The condition is an effect, so it can be effectful itself (a feature flag
lookup, a random value, a config read). When you already have a plain boolean,
wrap it with `Effect.succeed(condition)`:

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

const shouldLog = true

//      ┌─── Effect<Option<void>>
//      ▼
const program = Console.log("Condition is true!").pipe(
  // A plain boolean becomes a condition effect via Effect.succeed.
  Effect.when(Effect.succeed(shouldLog))
)
// => logs "Condition is true!" and yields Option.some(undefined)
```

## The filter family - keep, transform, or reject a value

The `filter*` combinators apply a predicate (or a [`Filter`](https://effect.plants.sh/data-types/)) to a
value. They differ on two axes: whether they operate on an **iterable's
elements** or on a **single effect's success value**, and on what happens when
the check **fails** (drop, fall back to another effect, or fail the effect).

The most common one is `filterOrFail` - a guard clause that fails when the value
is invalid, narrowing the type when you pass a refinement:

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

class TooSmall {
  readonly _tag = "TooSmall"
  constructor(readonly value: number) {}
}

const requirePositive = (n: number) =>
  Effect.succeed(n).pipe(
    Effect.filterOrFail(
      (x) => x > 0, // keep the value when this is true
      (x) => new TooSmall(x) // otherwise fail with this error
    )
  )
```

The full family is enumerated in the [reference](#filtering) below
(`filter`, `filterMap`, `filterMapEffect`, `filterOrElse`, `filterMapOrElse`,
`filterOrFail`, `filterMapOrFail`).

## `forEach` - run an effect for each element

`Effect.forEach` applies an effectful function to every element of an iterable
and collects the results into an array, preserving order. It short-circuits on
the first failure. The callback also receives the index.

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

const program = Effect.forEach([1, 2, 3, 4, 5], (n, index) =>
  Effect.log(`at index ${index}`).pipe(Effect.as(n * 2))
)
//      ▼ Effect<number[]> -> [2, 4, 6, 8, 10]
```

Pass `{ discard: true }` when you only care about the side effects and want to
skip building the result array (the effect then returns `void`). Pass a
`{ concurrency }` option to run the iterations concurrently - see
[Concurrency](https://effect.plants.sh/concurrency/).

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

// Run for every element, ignore the results.
const logAll = Effect.forEach(
  ["a", "b", "c"],
  (item) => Effect.log(item),
  { discard: true }
)
```

## `whileLoop` - loop while a condition holds

`Effect.whileLoop` repeatedly runs a `body` effect as long as a `while`
condition returns `true`, calling `step` with each value the body produces. The
`while` and `step` callbacks are plain (synchronous) functions; only `body` is an
effect. It is the effectful analogue of a `while` loop, useful for draining a
queue or polling until done.

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

const program = Effect.gen(function*() {
  const seen: Array<number> = []
  let counter = 0

  yield* Effect.whileLoop({
    // Keep going while this returns true.
    while: () => counter < 5,
    // The effect to run each iteration; here it increments the counter.
    body: () => Effect.sync(() => ++counter),
    // Called with each value the body produced - a plain `void` callback.
    step: (n) => seen.push(n)
  })

  return seen // [1, 2, 3, 4, 5]
})
```

For state that you accumulate across iterations, a plain `for`/`while` loop
inside `Effect.gen` (with `let` bindings and `yield*`) is often the most
readable choice - reach for `whileLoop` when you want the loop itself expressed
as a single composable effect.

## Looping a single effect

To repeat *one* effect (rather than iterate a collection), use `forever`,
`replicate`/`replicateEffect`, or - for policy-driven repetition with delays and
limits - `repeat` on a [`Schedule`](https://effect.plants.sh/scheduling/). These are covered in the
[reference](#repetition) below.

## Choosing an approach

- **Branching** - prefer native `if`/`else` inside `Effect.gen`; use
  `Effect.when` to conditionally run an effect within a pipeline.
- **Validating a value** - the filter family: `Effect.filterOrFail` to fail on
  an invalid value (narrowing with a refinement), `Effect.filterOrElse` to
  recover with a fallback effect, and `filterMap*` variants when a
  [`Filter`](https://effect.plants.sh/data-types/) both checks and transforms. Use `Effect.filter` /
  `Effect.filterMap` / `Effect.filterMapEffect` to filter an iterable's
  elements.
- **Iterating a collection** - `Effect.forEach` (with `discard`/`concurrency`
  options as needed).
- **Looping a single effect** - a `for`/`while` loop in `Effect.gen` for
  readability, `Effect.whileLoop` when you want a single composable effect,
  `Effect.forever` to run until interruption, `Effect.replicateEffect` to run a
  fixed number of times and collect, and `Effect.repeat` for policy-driven
  repetition.

For retrying and repeating effects on a policy, see
[Scheduling](https://effect.plants.sh/scheduling/); for combining many effects, see
[Building pipelines](https://effect.plants.sh/essentials/building-pipelines/).

## Reference

### Filtering

#### `filter`

Filters the elements of an iterable, keeping only those that satisfy a
predicate, refinement, or effectful predicate. A refinement narrows the element
type; an effectful predicate may take a `{ concurrency }` option.

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

// Sync predicate over an iterable
const evens = Effect.filter([1, 2, 3, 4], (n) => n % 2 === 0)
// => Effect<number[]> -> [2, 4]

// Effectful predicate
const checked = Effect.filter([1, 2, 3], (n) => Effect.succeed(n > 1))
// => Effect<number[]> -> [2, 3]
```

#### `filterMap`

Filters and maps iterable elements with a [`Filter`](https://effect.plants.sh/data-types/): each
`Result.succeed` value is collected, each `Result.fail` value is dropped.

```ts
import { Effect, Filter, Option } from "effect"

// A Filter that passes positive numbers, doubling them on the way through.
const positiveDoubled = Filter.fromPredicateOption((n: number) =>
  n > 0 ? Option.some(n * 2) : Option.none()
)

const doubled = Effect.filterMap([-1, 2, -3, 4], positiveDoubled)
// => Effect<number[]> -> [4, 8]

// The built-in Filter.string keeps only string values out of a mixed array.
const onlyStrings = Effect.filterMap([1, "a", 2, "b"], Filter.string)
// => Effect<string[]> -> ["a", "b"]
```

#### `filterMapEffect`

Like `filterMap`, but the `Filter` is effectful (a `FilterEffect`). Accepts an
optional `{ concurrency }`; with concurrency, results arrive in completion
order, not input order.

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

// FilterEffect: returns an Effect of a Result. succeed keeps & maps, fail drops.
const program = Effect.filterMapEffect(
  [1, 2, 3, 4],
  (n) => Effect.succeed(n % 2 === 0 ? Result.succeed(n * 10) : Result.fail(n))
)
// => Effect<number[]> -> [20, 40]
```

#### `filterOrElse`

Tests a single effect's success value against a predicate or refinement; when it
fails, runs an `orElse` effect (which receives the value) to produce a fallback.

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

const program = Effect.succeed(5).pipe(
  Effect.filterOrElse(
    (n) => n % 2 === 0, // keep when even
    (n) => Effect.succeed(`Number ${n} is odd`) // otherwise recover
  )
)
// => Effect<number | string> -> "Number 5 is odd"
```

#### `filterMapOrElse`

Validates and transforms a single effect's success value with a `Filter`; on
`Result.fail` it passes the failure value to an `orElse` fallback effect.

```ts
import { Console, Effect, Filter } from "effect"

const program = Effect.succeed<unknown>(42).pipe(
  Effect.filterMapOrElse(
    Filter.string, // succeed only for strings
    (notAString) => Console.log(`not a string: ${notAString}`).pipe(Effect.as("fallback"))
  )
)
// => Effect<string> -> logs "not a string: 42", yields "fallback"
```

#### `filterOrFail`

Tests a single effect's success value against a predicate or refinement; when it
fails, fails the effect with `orFailWith(value)`. Omit `orFailWith` to fail with
`NoSuchElementError`. A refinement narrows the success type.

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

// Refinement narrows `string | number` down to `number`.
const program = Effect.succeed<string | number>(7).pipe(
  Effect.filterOrFail(
    (x): x is number => typeof x === "number",
    (x) => `expected number, got ${typeof x}`
  )
)
// => Effect<number, string> -> succeeds with 7 (a number)
```

#### `filterMapOrFail`

Validates and transforms a single effect's success value with a `Filter`; on
`Result.fail` it fails the effect with `orFailWith(failureValue)` (or
`NoSuchElementError` when omitted).

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

const program = Effect.succeed<unknown>("hi").pipe(
  Effect.filterMapOrFail(
    Filter.string,
    (notAString) => `not a string: ${String(notAString)}`
  )
)
// => Effect<string, string> -> succeeds with "hi"
```

### Conditional operators

#### `when`

Runs the effect only when a **condition effect** succeeds with `true`, wrapping
the result in `Option.some`; when the condition is `false` the effect is skipped
and the result is `Option.none`. A failing condition propagates its failure.

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

const program = Console.log("ran!").pipe(Effect.when(Effect.succeed(true)))
// => Effect<Option<void>> -> logs "ran!", yields Option.some(undefined)

const skipped = Console.log("nope").pipe(Effect.when(Effect.succeed(false)))
// => Effect<Option<void>> -> yields Option.none(), logs nothing
```

For a plain boolean, wrap it with `Effect.succeed(bool)`. There is no
`Effect.unless` in v4 - negate the condition instead (`Effect.when(Effect.succeed(!bool))`).

### Looping

#### `forEach`

Runs an effectful function for every element of an iterable, collecting the
results in order and short-circuiting on the first failure. Options: `discard`
(return `void` instead of an array) and `concurrency`.

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

const program = Effect.forEach([1, 2, 3], (n) => Effect.succeed(n + 1))
// => Effect<number[]> -> [2, 3, 4]
```

#### `whileLoop`

Repeatedly runs `body` while the synchronous `while` predicate holds, threading
each produced value through the synchronous `step` callback. Only `body` is an
effect.

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

let n = 0
const program = Effect.whileLoop({
  while: () => n < 3,
  body: () => Effect.sync(() => n++),
  step: (value) => console.log(`step ${value}`)
})
// => Effect<void> -> logs "step 0", "step 1", "step 2"
```

### Repetition

#### `forever`

Repeats an effect endlessly - it only ends on failure or interruption, and its
success type is `never`. Pass `{ disableYield: true }` to skip the automatic
fiber yield on each iteration (tighter loop, less cooperative).

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

const tick = Effect.log("tick").pipe(Effect.delay("1 second"))

// Run forever in a child fiber, then interrupt after a while.
const program = Effect.gen(function*() {
  const fiber = yield* Effect.forkChild(Effect.forever(tick))
  yield* Effect.sleep("3 seconds")
  yield* Fiber.interrupt(fiber)
})
// => logs "tick" roughly every second until interrupted
```

#### `replicate`

Returns an `Array<Effect>` of `n` identical copies of the effect. It does **not**
run anything - it just builds the array, which you then hand to a collector such
as [`Effect.all`](https://effect.plants.sh/essentials/building-pipelines/).

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

const effects = Effect.replicate(Effect.succeed(1), 3)
// => Array<Effect<number>> of length 3 (nothing has run yet)

const program = Effect.all(effects)
// => Effect<number[]> -> [1, 1, 1] when run
```

#### `replicateEffect`

Runs the effect `n` times and collects the results, with `Effect.all` semantics.
Pass `{ concurrency }` to run the repetitions in parallel, or `{ discard: true }`
to ignore the results (the effect then returns `void`).

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

const program = Effect.replicateEffect(Effect.succeed(1), 3)
// => Effect<number[]> -> [1, 1, 1]

const discarded = Effect.replicateEffect(Effect.log("hi"), 3, { discard: true })
// => Effect<void> -> logs "hi" three times
```

#### `repeat`

Repeats a successful effect according to a [`Schedule`](https://effect.plants.sh/scheduling/) (or an
options object with `while` / `until` / `times` / `schedule`), stopping on the
first failure. The source always runs once before the schedule is stepped.

```ts
import { Console, Effect, Schedule } from "effect"

const action = Console.log("success")
const policy = Schedule.addDelay(Schedule.recurs(2), () => Effect.succeed("100 millis"))
const program = Effect.repeat(action, policy)
// => logs "success" three times (initial run + 2 repetitions)
```

See [Scheduling](https://effect.plants.sh/scheduling/) for the full set of schedule combinators and for
`retry` (the failure-driven counterpart of `repeat`).