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:
- Forgetting that a value might be missing (
null,undefined). - 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"
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.
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
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.
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:
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
| Exceptions | Result |
|---|---|
| Invisible in the function signature | Visible: Result<E, A> |
| Caller can forget to catch | Caller cannot read .value without checking .kind |
| Hard to narrow by error type | Error type is whatever discriminated union you choose |
| Stack unwinding is opaque | Errors flow as ordinary values you can map, log, transform |
| Unsuitable for predictable failures | Designed 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.
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
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
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