Maybe / Option
Maybe/Option is a sum type that says a value may be present or absent. If the value exists, it is wrapped as Some value or Just value; if it does not, the value is None or Nothing.
The important shift is that absence stops being a hidden exceptional condition and becomes an ordinary case in the data model. A caller can see from the return type itself that emptiness is part of the normal contract.
▶Architecture Diagram
📊 Data FlowDashed line animations indicate the flow direction of data or requests
If a function claims to return a string but sometimes returns null, the caller has to discover that fact indirectly: by reading docs, by defensive programming everywhere, or by crashing in production. The type signature hides a real branch in the program's behavior. Missing values are not rare accidents in many systems; they are normal outcomes. When the model hides that branch, every use site pays the cost in uncertainty and repeated checks.
After decades of bugs caused by null references, functional languages pushed toward modeling absence as data. Haskell's Maybe, ML-family Option, and Rust's Option all embody that move. The idea also fits cleanly with pure functions: instead of throwing or reaching into ambient state, a function returns a value that fully describes the outcome. As null-safety conversations spread into mainstream languages, Option-style modeling has become a practical design technique well beyond purely functional ecosystems.
Maybe/Option has a deliberately tiny structure: one value, two possible shapes. Some(value) means the data exists. None means it does not. Consumers typically handle it in three ways. First, they pattern match or switch over both variants. Second, they transform the inner value only when it exists by using map or andThen. Third, they finish with a default through unwrapOr or a similar combinator. All three approaches preserve the same rule: the missing-value case must be dealt with explicitly rather than leaking through as a surprise.
Lifting a nullable value into Option
type Option<T> =
| { kind: "some"; value: T }
| { kind: "none" };
function findUserNickname(id: string): Option<string> {
const nickname = db.users[id]?.nickname;
if (nickname == null) {
return { kind: "none" };
}
return { kind: "some", value: nickname };
}The function signature no longer hides that the value may be absent. Instead of leaking `null`, it returns one of two explicit shapes: `some` or `none`.
Applying the next transform only when a value exists
type Option<T> =
| { kind: "some"; value: T }
| { kind: "none" };
function map<T, U>(
option: Option<T>,
transform: (value: T) => U
): Option<U> {
return option.kind === "some"
? { kind: "some", value: transform(option.value) }
: { kind: "none" };
}
function unwrapOr<T>(option: Option<T>, fallback: T): T {
return option.kind === "some" ? option.value : fallback;
}
const upper = map(findUserNickname("42"), (nickname) => nickname.toUpperCase());
const label = unwrapOr(upper, "GUEST");
// If the value exists, it is transformed; otherwise the fallback is used.The next computation runs only for `some`, while `none` passes through unchanged. `unwrapOr` concentrates the fallback decision in one explicit place.
Maybe/Option and Either/Result both turn non-success states into values, but they carry different amounts of information. Maybe/Option only says whether a value exists. It does not explain why it is absent. Either/Result carries failure information alongside the branch itself. If absence alone is meaningful, Maybe/Option is the right level. If the caller needs to know why something failed, Either/Result is the better fit.
It is also different from plain nullable types. string | null does expose absence, but null often mixes loosely with the rest of the language and lacks a dedicated vocabulary for safe composition. Maybe/Option usually comes with targeted APIs and matching patterns that make the missing-value case much harder to forget.
Maybe/Option is strongest where absence is a normal and unsurprising state: optional profile data, cache lookups, search results, environment configuration that may not be present yet, and similar cases. In real code it is best used precisely, not everywhere. If every boundary is wrapped in Option by default, the model becomes noisy and callers spend all their time supplying fallback values. The concept works best when it marks a genuinely meaningful maybe-state, while richer failure cases move up to Result.