Option
Option<T> represents a value that may or may not exist. It is a type-safe alternative to null, undefined, or optional chaining.
Some<T>- a value of typeTis presentNone<T>- no value is present (absence)
Why Option?
Nullable values (null, undefined) are a common source of runtime errors. They're easy to forget, hard to track, and their meaning is often ambiguous - does null mean "not found", "not loaded yet", or "explicitly empty"?
Option makes absence explicit and type-safe. You can't accidentally use a missing value - the compiler forces you to handle both cases.
import { type Option, some, none } from "ok-fp/option";
// Without Option: null is ambiguous and easy to forget
function unsafeGet(id: string): User | null {
/* ... */
}
const user = unsafeGet("123");
console.log(user.name); // 💥 runtime error if null
// With Option: you must handle absence
function safeGet(id: string): Option<User> {
/* ... */
}
safeGet("123").match(
() => console.log("User not found"),
(user) => console.log(user.name),
);Basic Usage
Creating an Option
import { some, none, fromNullable } from "ok-fp/option";
const hasValue = some(42); // some(42)
const empty = none(); // none
const fromNull = fromNullable(null); // none
const fromValue = fromNullable("hello"); // some("hello")Transforming and Chaining
import { type Option, some, none } from "ok-fp/option";
const parseNumber = (input: string): Option<number> => {
const n = Number(input);
return Number.isFinite(n) ? some(n) : none();
};
const nonZero = (n: number): Option<number> => (n !== 0 ? some(n) : none());
const positive = (n: number): Option<number> => (n > 0 ? some(n) : none());
const compute = (input: string): Option<number> =>
parseNumber(input)
.flatMap(nonZero) // must not be 0
.flatMap(positive) // must be > 0
.map((n) => 1 / n) // reciprocal (no need for flatMap)
.map((n) => n * 100); // scale
// Usage (returns Option instances)
compute("4"); // returns some(25)
compute("0"); // returns none() (fails nonZero)
compute("-3"); // returns none() (fails positive)
compute("abc"); // returns none() (fails parsing)Extracting the Value
import { type Option, some, none } from "ok-fp/option";
const x: Option<number> = some(42);
x.getOrElse(() => 0); // 42
const y: Option<number> = none();
y.getOrElse(() => 0); // 0
// Pattern matching
const result = x.match(
() => "no value", // None case
(val) => `value: ${val}`, // some case
);
// result = "value: 42"API Reference
some
some<T>(value: T): Option<T>Create an Option containing a value.
some(42); // some(42)
some("hello"); // some("hello")none
none<T>(): Option<T>Create an Option representing absence.
none(); // none
none<number>(); // none (typed)fromNullable
fromNullable<T>(value: T | null | undefined): Option<T>Convert a nullable value to Option. Returns some if the value is not null/undefined, otherwise None.
fromNullable(42); // some(42)
fromNullable(null); // none
fromNullable(undefined); // nonefromEither
fromEither<L, R>(either: Either<L, R>): Option<R>Convert an Either to an Option. Right becomes some, Left becomes None.
fromEither(right(42)); // some(42)
fromEither(left("error")); // nonemap
map<U>(f: (value: T) => U): Option<U>Transform the value if present. Returns None if the Option is None.
some(5).map((n) => n * 2); // some(10)
none<number>().map((n) => n * 2); // nonefilter
filter(predicate: (value: T) => boolean): Option<T>Keep the value only if the predicate holds. Returns None otherwise.
some(5).filter((n) => n > 3); // some(5)
some(2).filter((n) => n > 3); // noneflatMap
flatMap<U>(f: (value: T) => Option<U>): Option<U>Chain Option-returning operations. Short-circuits to None if this Option is None.
const safeDivide = (x: number) => (x === 0 ? none() : some(10 / x));
some(2).flatMap(safeDivide); // some(5)
some(0).flatMap(safeDivide); // none
none().flatMap(safeDivide); // noneflatten
flatten(): Option<U> // where this is Option<Option<U>>Remove one level of nesting from a nested Option.
some(some(5)).flatten(); // some(5)
some(none<number>()).flatten(); // noneorElse
orElse(fallback: () => Option<T>): Option<T>Return this Option if it contains a value, otherwise return the fallback. The fallback is lazily evaluated.
some(5).orElse(() => some(10)); // some(5)
none().orElse(() => some(10)); // some(10)zip
zip<A>(optA: Option<A>): Option<readonly [T, A]>Combine two Options into a tuple. Returns None if either is None.
some("Alice").zip(some(30)); // some(["Alice", 30])
some("Alice").zip(none()); // noneap
ap<A, U>(this: Option<(arg: A) => U>, optA: Option<A>): Option<U>Apply a function wrapped in an Option to a value wrapped in an Option.
const add = (x: number) => (y: number) => x + y;
some(add(5)).ap(some(3)); // some(8)
some(add(5)).ap(none()); // nonetap
tap(sideEffect: (value: T) => unknown): Option<T>Run a side effect if some. Returns the original Option unchanged.
some(5).tap((v) => console.log("value:", v)); // some(5), logs "value: 5"
none().tap((v) => console.log("value:", v)); // none, no logtapNone
tapNone(sideEffect: () => unknown): Option<T>Run a side effect if None. Returns the original Option unchanged.
some(5).tapNone(() => console.log("empty")); // some(5), no log
none().tapNone(() => console.log("empty")); // none, logs "empty"match
match<U>(onNone: () => U, onSome: (value: T) => U): UPattern match on the Option.
some(5).match(
() => "empty",
(x) => `value: ${x}`,
); // "value: 5"
none().match(
() => "empty",
(x) => `value: ${x}`,
); // "empty"getOrElse
getOrElse(fallback: () => T): TExtract the value, or return a fallback if None. The fallback is lazily evaluated.
some(5).getOrElse(() => 0); // 5
none().getOrElse(() => 0); // 0toNullable
toNullable(): T | nullConvert the Option to a nullable value. Returns the value if some, null if None.
some(5).toNullable(); // 5
none().toNullable(); // nulltoArray
toArray(): readonly T[]Convert the Option to an array. Returns a single-element array if some, empty array if None.
some(42).toArray(); // [42]
none().toArray(); // []map2
map2<A, B, C>(optA: Option<A>, optB: Option<B>, f: (a: A, b: B) => C): Option<C>Combine two Options with a function. Returns None if either is None.
map2(some("John"), some("Doe"), (f, l) => `${f} ${l}`); // some("John Doe")
map2(some("John"), none(), (f, l) => `${f} ${l}`); // nonemap3
map3<A, B, C, D>(optA: Option<A>, optB: Option<B>, optC: Option<C>, f: (a: A, b: B, c: C) => D): Option<D>Combine three Options with a function. Returns None if any is None.
map3(some(1), some(2), some(3), (a, b, c) => a + b + c); // some(6)sequence
sequence<T>(opts: Option<T>[]): Option<T[]>Convert an array of Options to an Option of array. Returns None if any element is None.
sequence([some(1), some(2), some(3)]); // some([1, 2, 3])
sequence([some(1), none(), some(3)]); // none