Option
Option<A> models a value that may be absent. It is either Some<A>, holding a
value of type A, or None, representing no value. Use it instead of
null/undefined when absence is a normal, expected outcome — looking up a key,
parsing input, reading an optional field. The type then makes the missing case
visible and the compiler forces you to handle it.
import { Option } from "effect"
// A partial function: defined only for non-empty arraysconst head = <A>(array: ReadonlyArray<A>): Option.Option<A> => array.length > 0 ? Option.some(array[0]) : Option.none()
// Handle both cases explicitly — no `undefined` checks leaking downstreamconst describe = <A>(array: ReadonlyArray<A>): string => Option.match(head(array), { onNone: () => "the array is empty", onSome: (value) => `the first element is ${value}` })
console.log(describe([1, 2, 3])) // "the first element is 1"console.log(describe([])) // "the array is empty"head returns an Option rather than A | undefined, so every caller must
decide what to do when the value is missing. Option.match is the safe way to
do that: it takes one callback per case and returns a single value.
Creating an Option
Section titled “Creating an Option”import { Option } from "effect"
const value = Option.some(1) // { _id: 'Option', _tag: 'Some', value: 1 }const empty = Option.none() // { _id: 'Option', _tag: 'None' }Option.none() is a function call (not a constant) so it can infer the element
type at the use site.
To build an Option from a predicate, use Option.liftPredicate:
import { Option } from "effect"
// ┌─── (n: number) => Option<number>// ▼const parsePositive = Option.liftPredicate((n: number) => n > 0)
console.log(parsePositive(10)) // { _id: 'Option', _tag: 'Some', value: 10 }console.log(parsePositive(-1)) // { _id: 'Option', _tag: 'None' }Modeling optional fields
Section titled “Modeling optional fields”Use Option<A> for properties that may have no value. The key is always
present; only the value is optional, which is exactly what Option expresses.
import { Option } from "effect"
interface User { readonly id: number readonly username: string // The `email` key is always present; the value may be absent readonly email: Option.Option<string>}
const withEmail: User = { id: 1, username: "john_doe", email: Option.some("john.doe@example.com")}
const withoutEmail: User = { id: 2, username: "jane_doe", email: Option.none()}Transforming and chaining
Section titled “Transforming and chaining”Option.map transforms the value inside a Some and leaves a None untouched.
Option.flatMap is for functions that themselves return an Option, letting
you walk nested optional data without manual checks.
import { Option } from "effect"
interface Address { readonly city: string readonly street: Option.Option<string>}interface User { readonly address: Option.Option<Address>}
const user: User = { address: Option.some({ city: "New York", street: Option.some("123 Main St") })}
// If `address` is None, the chain short-circuits to None.// Otherwise we dig into the nested optional `street`.const street = user.address.pipe( Option.flatMap((address) => address.street))
console.log(street) // { _id: 'Option', _tag: 'Some', value: '123 Main St' }Option.filter keeps the value only if a predicate holds, turning it into
None otherwise:
import { Option } from "effect"
// Treat an empty string as "no value"const nonEmpty = (input: Option.Option<string>) => Option.filter(input, (value) => value !== "")
console.log(nonEmpty(Option.some(""))) // { _id: 'Option', _tag: 'None' }console.log(nonEmpty(Option.some("a"))) // { _id: 'Option', _tag: 'Some', value: 'a' }Extracting the value
Section titled “Extracting the value”To get a plain value back out, supply a fallback so the None case is always
handled:
import { Option } from "effect"
// Provide a default for the None caseconsole.log(Option.getOrElse(Option.some(5), () => 0)) // 5console.log(Option.getOrElse(Option.none(), () => 0)) // 0
// Interop with code that expects null / undefinedconsole.log(Option.getOrNull(Option.none())) // nullconsole.log(Option.getOrUndefined(Option.some(5))) // 5
// Throws if None — use only at boundaries where None is truly impossibleconsole.log(Option.getOrThrow(Option.some(10))) // 10Fallbacks
Section titled “Fallbacks”Option.orElse tries an alternative when the first Option is None, and
Option.firstSomeOf returns the first Some from a list:
import { Option } from "effect"
const fromCache = Option.none<number>()const fromDb = Option.some(42)
console.log(Option.orElse(fromCache, () => fromDb))// { _id: 'Option', _tag: 'Some', value: 42 }
console.log( Option.firstSomeOf([Option.none(), Option.some(2), Option.some(3)]))// { _id: 'Option', _tag: 'Some', value: 2 }Interop with nullable values
Section titled “Interop with nullable values”Option.fromNullishOr converts null/undefined into None, and any other
value into Some. It is the bridge between Effect code and APIs that return
nullable values.
import { Option } from "effect"
console.log(Option.fromNullishOr(null)) // { _id: 'Option', _tag: 'None' }console.log(Option.fromNullishOr(undefined)) // { _id: 'Option', _tag: 'None' }console.log(Option.fromNullishOr(1)) // { _id: 'Option', _tag: 'Some', value: 1 }Going the other way, Option.getOrNull / Option.getOrUndefined turn a None
back into null / undefined.
Combining multiple Options
Section titled “Combining multiple Options”Option.all combines several Options into one, preserving the input shape
(tuple, struct, or iterable). If any input is None, the result is None.
import { Option } from "effect"
const maybeName: Option.Option<string> = Option.some("John")const maybeAge: Option.Option<number> = Option.some(25)
// ┌─── Option<{ name: string; age: number }>// ▼const struct = Option.all({ name: maybeName, age: maybeAge })console.log(struct)// { _id: 'Option', _tag: 'Some', value: { name: 'John', age: 25 } }
// One None makes the whole thing Noneconsole.log(Option.all([Option.some("John"), Option.none()]))// { _id: 'Option', _tag: 'None' }To combine two values with a function, use Option.zipWith.
Generator syntax
Section titled “Generator syntax”Like Effect.gen, Option.gen lets you write sequential code that
short-circuits on the first None. Each yield* either unwraps a Some or
aborts the whole block.
import { Option } from "effect"
const maybeName = Option.some("John")const maybeAge = Option.some(25)
const person = Option.gen(function* () { const name = (yield* maybeName).toUpperCase() const age = yield* maybeAge return { name, age }})
console.log(person)// { _id: 'Option', _tag: 'Some', value: { name: 'JOHN', age: 25 } }If maybeName were None, the generator would stop immediately and return
None — age would never be read. Keep these generators pure: Option is a
data structure, not an effect, so avoid side effects inside them.
Interop with Effect
Section titled “Interop with Effect”An Option can be used directly inside Effect.gen. A Some<A> yields its
value; a None fails the effect with a NoSuchElementError, which you can then
handle with the usual Error Management combinators.
import { Effect, Option } from "effect"
const program = Effect.gen(function* () { // `yield*` on a Some produces the value; // a None would fail with NoSuchElementError const x = yield* Option.some(10) const y = yield* Option.some(5) return x + y})
Effect.runPromise(program).then(console.log) // 15This makes Option a convenient way to express “may be missing” steps inside an
otherwise effectful workflow.