Dataslope logoDataslope

Option and Result

Two tiny algebraic types that replace `null`, `undefined`, and exceptions with values the compiler tracks.

The two most common sources of bugs in everyday TypeScript are:

  1. Forgetting that a value might be missing (null, undefined).
  2. Forgetting that a function might fail (exceptions).

Both are invisible in JavaScript: null looks like a value, an exception flies up the stack invisibly through every function it passes. Both can be fixed by the same idea: encode possibility in the type, so the compiler forces you to think about it.

The two types are Option<A> and Result<E, A>. They are the single most useful pair of patterns in this entire course.

Option: "maybe there is an A"

Code Block
TypeScript 5.7

The compiler refuses to let you read .value until you've checked .kind === "some". That's the safety: every read is preceded by a check, and the compiler enforces it.

Operations on Option

The whole point of these types is that you don't have to keep switch-ing manually. You define a small library of operations once, and from then on you transform Option values directly.

Code Block
TypeScript 5.7

Three operators: map (transform inside), flatMap (chain operations that also return Option), getOrElse (escape with a fallback). With these three, you can compose long chains of "this might or might not be there" without a single if.

A pipeline of fallible lookups

Code Block
TypeScript 5.7

No null checks, no nested ternaries, no exceptions. If any step returns none, the whole chain short-circuits to none and the fallback kicks in.

Result: "an A or an error E"

Option answers "is there a value?" Result answers "what went wrong?". You use it when you need information about the failure, not just its absence.

Code Block
TypeScript 5.7

The error type is generic. You can use a string, an enum, a discriminated union of structured errors — anything. We'll see in Functional Error Handling how structured error types let you express your domain's failure modes precisely.

Operations on Result

Same three operators, with error-handling versions:

Code Block
TypeScript 5.7

The contract is the same in every step: if ok, transform; if err, pass through unchanged. Errors propagate without explicit re-throwing. The pipeline short-circuits the moment something fails.

Why this is so much better than exceptions

ExceptionsResult
Invisible in the function signatureVisible: Result<E, A>
Caller can forget to catchCaller cannot read .value without checking .kind
Hard to narrow by error typeError type is whatever discriminated union you choose
Stack unwinding is opaqueErrors flow as ordinary values you can map, log, transform
Unsuitable for predictable failuresDesigned for them

Exceptions remain valid for truly unexpected situations ("invariant broken", "out of memory"). Result is for expected failures: validation, parsing, I/O outcomes, business rule violations.

The diagram of flatMap

flatMap (also called chain, bind, or andThen) is the operator that turns "a chain of fallible steps" into "one composed computation". Every later chapter will use it for some M<A>: Option<A>, Result<E, A>, Task<A>, Promise<A>.

The diagram captures the idea exactly: flatMap threads the "surrounding context" (failure, asynchrony, nondeterminism) through the chain, so each step can pretend it just sees an ordinary A.

Converting at the boundary

Many APIs return nullable values or throw. Wrap them at the boundary so the rest of your code only sees Option/Result.

Code Block
TypeScript 5.7

The boundary is the only place exceptions and nullables touch your code. From there inward, you work in the type system.

A multi-file challenge

Challenge
TypeScript 5.7
Find a user's manager's email

In lookups.ts, implement emailOfManagerOf(id) using flatMap and map on Option. Do not use if or switch inside this function — chain the helpers.

The helpers findUser, managerOf, emailOf are already provided.

Expected output

u3 -> linus@example.com
u2 -> ada@example.com
u1 -> none
ux -> none

QuestionSelect one

Why does Result<E, A> make error handling safer than throwing exceptions?

It is faster than throw at runtime

It hides errors from the caller

The error possibility appears in the function's return type, so the caller is forced by the compiler to handle it

It eliminates the need for the try/catch keyword

On this page