Skip to content

Result

Result<A, E> represents a computation that either succeeded with a value of type A or failed with an error of type E. It is a Success<A, E> or a Failure<A, E>. Use it for synchronous operations that can fail in a known way — parsing, validation, decoding — when you want the failure in the return type rather than as a thrown exception.

In Effect v4, Result replaces v3’s Either. The success type comes first (Result<A, E>, mirroring Effect<A, E, R>), the constructors are Result.succeed / Result.fail, and the payloads are read via .success and .failure.

import { Result } from "effect"
// A synchronous parser that reports failure as a value, not an exception
const parseAge = (input: string): Result.Result<number, string> => {
const n = Number(input)
return Number.isInteger(n) && n >= 0
? Result.succeed(n)
: Result.fail(`"${input}" is not a valid age`)
}
const message = Result.match(parseAge("42"), {
onFailure: (error) => `Invalid: ${error}`,
onSuccess: (age) => `Parsed age ${age}`
})
console.log(message) // "Parsed age 42"

parseAge never throws. Its signature, Result<number, string>, tells callers both outcomes are possible, and Result.match forces them to handle each.

import { Result } from "effect"
const ok = Result.succeed(42) // { _id: 'Result', _tag: 'Success', success: 42 }
const err = Result.fail("not found") // { _id: 'Result', _tag: 'Failure', failure: 'not found' }

Result.fromOption lifts an Option into a Result, supplying the failure to use for None:

import { Option, Result } from "effect"
const required = Result.fromOption(
Option.fromNullishOr(process.env.PORT),
() => "PORT is not set"
)

Result.isSuccess and Result.isFailure narrow the type so you can access the payload safely:

import { Result } from "effect"
const result: Result.Result<number, string> = Result.succeed(42)
if (Result.isFailure(result)) {
console.log(`Failed: ${result.failure}`)
} else {
console.log(`Succeeded: ${result.success}`)
}
// "Succeeded: 42"

Result.map transforms a success and passes failures through unchanged; Result.mapError does the opposite. Result.flatMap chains a step that itself returns a Result, short-circuiting on the first failure.

import { Result } from "effect"
const parse = (s: string): Result.Result<number, string> =>
Number.isNaN(Number(s)) ? Result.fail(`not a number: ${s}`) : Result.succeed(Number(s))
const positive = (n: number): Result.Result<number, string> =>
n > 0 ? Result.succeed(n) : Result.fail("must be positive")
// Chain two fallible steps; either failure stops the pipeline
const program = parse("10").pipe(
Result.flatMap(positive),
Result.map((n) => n * 2)
)
console.log(program) // { _id: 'Result', _tag: 'Success', success: 20 }
import { Result } from "effect"
// Supply a default for the failure case
console.log(Result.getOrElse(Result.fail("oops"), (e) => `recovered from ${e}`))
// "recovered from oops"
// Collapse into a single union value
console.log(Result.merge(Result.fail("oops"))) // "oops"
// Throw the raw failure — use only where failure is impossible
console.log(Result.getOrThrow(Result.succeed(1))) // 1

Result.gen reads like Effect.gen but short-circuits on the first Failure. Each yield* either unwraps a success or aborts the block with that failure.

import { Result } from "effect"
const parse = (s: string): Result.Result<number, string> =>
Number.isNaN(Number(s)) ? Result.fail(`not a number: ${s}`) : Result.succeed(Number(s))
const program = Result.gen(function* () {
const x = yield* parse("10")
const y = yield* parse("5")
return x + y
})
console.log(program) // { _id: 'Result', _tag: 'Success', success: 15 }

A Result can be used directly inside Effect.gen: a Success yields its value, a Failure fails the effect with E. This makes Result a natural way to write synchronous, fallible steps inside an effectful workflow without juggling two error channels.

import { Effect, Result } from "effect"
const parse = (s: string): Result.Result<number, string> =>
Number.isNaN(Number(s)) ? Result.fail(`not a number: ${s}`) : Result.succeed(Number(s))
const program = Effect.gen(function* () {
// A Failure here propagates as an Effect failure of type string
const x = yield* parse("10")
const y = yield* parse("5")
return x + y
})
Effect.runPromise(program).then(console.log) // 15

Result is for synchronous, expected failures of type E. When you run an effect, the outcome is an Exit, which wraps a Cause rather than a bare E — it can also represent defects and interruptions. Reach for Result in pure code, and for Exit when inspecting the result of executing an Effect. See Result and Exit for how the two relate in error management.

Everything below is a public export of the Result module. Each entry has a short, runnable example with inline // => comments. Most data-last combinators (map, flatMap, …) are dual: you can call them Result.map(self, f) or in a pipeline as self.pipe(Result.map(f)).

Lifts a value into a successful Result. The error type defaults to never.

import { Result } from "effect"
Result.succeed(42)
// => { _id: 'Result', _tag: 'Success', success: 42 }

Creates a failed Result. The success type defaults to never.

import { Result } from "effect"
Result.fail("boom")
// => { _id: 'Result', _tag: 'Failure', failure: 'boom' }

A pre-built Result<void> that succeeds with undefined. Use when a success should signal completion without carrying a meaningful value.

import { Result } from "effect"
const done: Result.Result<void> = Result.void
Result.isSuccess(done)
// => true

A pre-built Result<never, void> whose failure value is undefined. Use when a failure is only a control signal and no payload is needed.

import { Result } from "effect"
Result.failVoid
// => { _id: 'Result', _tag: 'Failure', failure: undefined }

A pre-built Result<Option<never>> that succeeds with None. Handy as the absent branch when working with transposeOption.

import { Result } from "effect"
Result.succeedNone
// => { _id: 'Result', _tag: 'Success', success: { _id: 'Option', _tag: 'None' } }

Creates a Result<Option<A>> that succeeds with Some(a).

import { Result } from "effect"
Result.succeedSome(42)
// => { _id: 'Result', _tag: 'Success', success: { _id: 'Option', _tag: 'Some', value: 42 } }

Runs a synchronous computation that may throw, capturing the thrown value as a Failure. With a single function the failure type is unknown; with { try, catch } the catch function maps the thrown value to E.

import { Result } from "effect"
Result.try(() => JSON.parse('{"ok":true}'))
// => { _id: 'Result', _tag: 'Success', success: { ok: true } }
Result.try({
try: () => JSON.parse("not json"),
catch: (e) => `Parse failed: ${e}`
})
// => { _id: 'Result', _tag: 'Failure', failure: 'Parse failed: SyntaxError: ...' }

Turns a possibly null/undefined value into a Result. Non-nullish values become Success<NonNullable<A>>; null/undefined become Failure<E> using the onNullish callback (which receives the original value).

import { Result } from "effect"
Result.fromNullishOr(1, () => "missing")
// => { _id: 'Result', _tag: 'Success', success: 1 }
Result.fromNullishOr(null, () => "missing")
// => { _id: 'Result', _tag: 'Failure', failure: 'missing' }

Converts an Option<A> into a Result<A, E>. Some becomes Success; None becomes Failure, with the error produced by the onNone thunk.

import { Option, Result } from "effect"
Result.fromOption(Option.some(1), () => "missing")
// => { _id: 'Result', _tag: 'Success', success: 1 }
Result.fromOption(Option.none(), () => "missing")
// => { _id: 'Result', _tag: 'Failure', failure: 'missing' }

Swaps Option<Result<A, E>> into Result<Option<A>, E>. None becomes Success(None), Some(Success(a)) becomes Success(Some(a)), and Some(Failure(e)) becomes Failure(e).

import { Option, Result } from "effect"
Result.transposeOption(Option.some(Result.succeed(42)))
// => Success(Some(42))
Result.transposeOption(Option.none<Result.Result<number, string>>())
// => Success(None)
Result.transposeOption(Option.some(Result.fail("err")))
// => { _id: 'Result', _tag: 'Failure', failure: 'err' }

Maps an Option value with a Result-returning function and transposes in one step, producing Result<Option<B>, E>. None short-circuits to Success(None) without calling the function.

import { Option, Result } from "effect"
const parse = (s: string) =>
isNaN(Number(s)) ? Result.fail("not a number") : Result.succeed(Number(s))
Result.transposeMapOption(Option.some("42"), parse)
// => Success(Some(42))
Result.transposeMapOption(Option.none(), parse)
// => Success(None)

Type guard that returns true for any Result (Success or Failure), narrowing unknown input to Result<unknown, unknown>.

import { Result } from "effect"
Result.isResult(Result.succeed(1))
// => true
Result.isResult({ value: 1 })
// => false

Type guard narrowing a Result to Success<A, E>, after which .success is accessible.

import { Result } from "effect"
const r: Result.Result<number, string> = Result.succeed(42)
Result.isSuccess(r) ? r.success : -1
// => 42

Type guard narrowing a Result to Failure<A, E>, after which .failure is accessible.

import { Result } from "effect"
const r: Result.Result<number, string> = Result.fail("oops")
Result.isFailure(r) ? r.failure : ""
// => "oops"

Extracts the success value as an Option, discarding the failure. Success<A> becomes Some<A>; Failure<E> becomes None.

import { Result } from "effect"
Result.getSuccess(Result.succeed("ok"))
// => { _id: 'Option', _tag: 'Some', value: 'ok' }
Result.getSuccess(Result.fail("err"))
// => { _id: 'Option', _tag: 'None' }

Extracts the failure value as an Option, discarding the success. Failure<E> becomes Some<E>; Success<A> becomes None.

import { Result } from "effect"
Result.getFailure(Result.fail("err"))
// => { _id: 'Option', _tag: 'Some', value: 'err' }
Result.getFailure(Result.succeed("ok"))
// => { _id: 'Option', _tag: 'None' }

Transforms the success value, passing failures through unchanged.

import { Result } from "effect"
Result.map(Result.succeed(3), (n) => n * 2)
// => { _id: 'Result', _tag: 'Success', success: 6 }
Result.map(Result.fail("err"), (n: number) => n * 2)
// => { _id: 'Result', _tag: 'Failure', failure: 'err' }

Transforms the failure value, passing successes through unchanged.

import { Result } from "effect"
Result.mapError(Result.fail("not found"), (e) => `Error: ${e}`)
// => { _id: 'Result', _tag: 'Failure', failure: 'Error: not found' }

Transforms both channels at once with { onSuccess, onFailure }, without changing whether the result succeeds or fails.

import { Result } from "effect"
Result.mapBoth(Result.succeed(1), {
onSuccess: (n) => n + 1,
onFailure: (e: string) => `Error: ${e}`
})
// => { _id: 'Result', _tag: 'Success', success: 2 }

Chains a function returning a Result onto a success, merging the error types into a union. Short-circuits on the first Failure. This is the monadic bind.

import { Result } from "effect"
Result.flatMap(Result.succeed(5), (n) =>
n > 0 ? Result.succeed(n * 2) : Result.fail("not positive")
)
// => { _id: 'Result', _tag: 'Success', success: 10 }

A flexible flatMap that also accepts a plain value, a plain-returning function, or a Result directly. If self is a Failure, the argument is never evaluated.

import { Result } from "effect"
Result.andThen(Result.succeed(1), (n) => Result.succeed(n + 1))
// => Success(2)
Result.andThen(Result.succeed(1), (n) => n + 1)
// => Success(2)
Result.andThen(Result.succeed(1), "done")
// => Success("done")

Swaps the success and failure channels, turning Result<A, E> into Result<E, A>.

import { Result } from "effect"
Result.flip(Result.succeed(42))
// => { _id: 'Result', _tag: 'Failure', failure: 42 }
Result.flip(Result.fail("error"))
// => { _id: 'Result', _tag: 'Success', success: 'error' }

Runs a side-effect on the success value (e.g. logging) and returns the original Result unchanged. The callback’s return value is ignored; it is not called on a Failure.

import { Result } from "effect"
Result.tap(Result.succeed(42), (n) => console.log("Got:", n))
// logs: "Got: 42"
// => { _id: 'Result', _tag: 'Success', success: 42 }

Builds a Result from a raw value guarded by a predicate or refinement. Passing values become Success; failing ones use orFailWith to produce the error. A Refinement narrows the success type.

import { Result } from "effect"
Result.liftPredicate(5, (n: number) => n > 0, (n) => `${n} is not positive`)
// => { _id: 'Result', _tag: 'Success', success: 5 }
Result.liftPredicate(-1, (n: number) => n > 0, (n) => `${n} is not positive`)
// => { _id: 'Result', _tag: 'Failure', failure: '-1 is not positive' }

Validates the success value of an existing Result with a predicate or refinement. An existing Failure passes through; a passing predicate keeps the Success; a failing one produces a new Failure via orFailWith. The output error type is the union of both.

import { Result } from "effect"
Result.filterOrFail(
Result.succeed(0),
(n) => n > 0,
(n) => `${n} is not positive`
)
// => { _id: 'Result', _tag: 'Failure', failure: '0 is not positive' }

Returns the inner value as A | E regardless of the variant — useful when both channels share a compatible type.

import { Result } from "effect"
Result.merge(Result.succeed(42))
// => 42
Result.merge(Result.fail("error"))
// => "error"

Returns the success value, or computes a fallback from the failure value.

import { Result } from "effect"
Result.getOrElse(Result.succeed(1), () => 0)
// => 1
Result.getOrElse(Result.fail("err"), (e) => `recovered: ${e}`)
// => "recovered: err"

Returns the success value, or null on failure.

import { Result } from "effect"
Result.getOrNull(Result.succeed(1))
// => 1
Result.getOrNull(Result.fail("err"))
// => null

Returns the success value, or undefined on failure.

import { Result } from "effect"
Result.getOrUndefined(Result.succeed(1))
// => 1
Result.getOrUndefined(Result.fail("err"))
// => undefined

Returns the success value, or throws the raw failure value E. Use only at boundaries where failure is unexpected.

import { Result } from "effect"
Result.getOrThrow(Result.succeed(1))
// => 1
Result.getOrThrow(Result.fail("error"))
// => throws "error"

Returns the success value, or throws a custom error derived from the failure.

import { Result } from "effect"
Result.getOrThrowWith(Result.succeed(1), () => new Error("fail"))
// => 1
Result.getOrThrowWith(Result.fail("oops"), (e) => new Error(`Unexpected: ${e}`))
// => throws Error("Unexpected: oops")

Folds a Result into a single value by applying onSuccess or onFailure. Both branches must produce a common type.

import { Result } from "effect"
const format = Result.match({
onSuccess: (n: number) => `Got ${n}`,
onFailure: (e: string) => `Err: ${e}`
})
format(Result.succeed(42))
// => "Got 42"
format(Result.fail("timeout"))
// => "Err: timeout"

Returns the original Result if it is a Success; otherwise applies that to the error to produce a recovery Result.

import { Result } from "effect"
Result.orElse(Result.fail("primary failed"), () => Result.succeed(99))
// => { _id: 'Result', _tag: 'Success', success: 99 }
Result.orElse(Result.succeed(1), () => Result.succeed(99))
// => { _id: 'Result', _tag: 'Success', success: 1 }

Collects a tuple, struct, or iterable of Results into a single Result of the collected values, preserving structure. Short-circuits on the first Failure — later elements are not inspected.

import { Result } from "effect"
// Tuple
Result.all([Result.succeed(1), Result.succeed("two")])
// => Success([1, "two"])
// Struct
Result.all({ x: Result.succeed(1), y: Result.fail("err") })
// => { _id: 'Result', _tag: 'Failure', failure: 'err' }
// Iterable
Result.all(new Set([Result.succeed(1), Result.succeed(2)]))
// => Success([1, 2])

Builds an Equivalence for Result from equivalences for the success and failure types. Two Success values are equal by the success equivalence, two Failure values by the failure equivalence, and a Success is never equal to a Failure.

import { Equivalence, Result } from "effect"
const eq = Result.makeEquivalence(
Equivalence.strictEqual<number>(),
Equivalence.strictEqual<string>()
)
eq(Result.succeed(1), Result.succeed(1))
// => true
eq(Result.succeed(1), Result.fail("x"))
// => false

Generator-based composition. yield* unwraps a Success or short-circuits on a Failure; the generator’s return value is wrapped in Success. Evaluated eagerly and synchronously.

import { Result } from "effect"
Result.gen(function* () {
const a = yield* Result.succeed(1)
const b = yield* Result.succeed(2)
return a + b
})
// => { _id: 'Result', _tag: 'Success', success: 3 }

The starting point for do-notation: a Result<{}> (success with an empty object) to which you add named fields with bind and let.

import { pipe, Result } from "effect"
pipe(
Result.Do,
Result.bind("x", () => Result.succeed(2)),
Result.bind("y", () => Result.succeed(3)),
Result.let("sum", ({ x, y }) => x + y)
)
// => Success({ x: 2, y: 3, sum: 5 })

Adds a named field to the do-notation accumulator by running a Result-producing function over the current object. Short-circuits on the first Failure.

import { pipe, Result } from "effect"
pipe(
Result.Do,
Result.bind("x", () => Result.succeed(2)),
Result.bind("y", ({ x }) => Result.succeed(x + 3))
)
// => Success({ x: 2, y: 5 })

Adds a named field to the do-notation accumulator from a pure (non-Result) computation over the current object.

import { pipe, Result } from "effect"
pipe(
Result.Do,
Result.bind("x", () => Result.succeed(2)),
Result.let("doubled", ({ x }) => x * 2)
)
// => Success({ x: 2, doubled: 4 })

Wraps the success value of an existing Result into a named field — typically to start a do-notation chain from a Result you already have.

import { pipe, Result } from "effect"
pipe(Result.succeed(42), Result.bindTo("answer"))
// => Success({ answer: 42 })
  • Option — represent optional values; convert with fromOption / getSuccess / transposeOption.
  • Exit and Cause — the outcome of running an Effect, with a full Cause instead of a bare E.
  • Result and Exit — using both in error management.