TypeScript gives you strong guarantees about many kinds of invalid input.
What it does not do is track thrown exceptions through your program in a useful way.
That is why expected failures often read more cleanly as data than as exceptions.
Not Every Failure Should Throw
There is a difference between:
- "the payment gateway returned
card_declined"
- "our process hit an invariant that should never happen"
The first is part of normal business flow.
The second is exceptional.
If you throw for both, callers have to infer too much from control flow.
A Simple Result Type
You do not need category theory to use the idea well. In TypeScript, a discriminated union is enough:
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
async function fetchUser(id: string): Promise<Result<User, "NOT_FOUND" | "NETWORK_ERROR">> {
const response = await fetch(`/api/users/${id}`);
if (response.status === 404) {
return { ok: false, error: "NOT_FOUND" };
}
if (!response.ok) {
return { ok: false, error: "NETWORK_ERROR" };
}
return { ok: true, value: await response.json() };
}
Now the function signature tells the caller that failure is expected and modeled.
What You Gain
This approach helps when:
- the failure modes are part of domain logic
- the caller is expected to react differently to each one
- you want those cases visible in the type system
The call site becomes explicit:
const result = await fetchUser("123");
if (!result.ok) {
if (result.error === "NOT_FOUND") return render404();
return renderRetryState();
}
return renderProfile(result.value);
That is often easier to review than a try/catch that mixes transport errors, parser errors, and business logic failures in one block.
What You Should Still Throw
Result types are not a reason to ban exceptions.
Throw when:
- the process hit a true invariant violation
- continuing execution is unsafe
- a framework boundary expects exceptions
Model as data when:
- the failure is expected
- the caller needs to branch on it
- it is part of normal product behavior
That line is more useful than "exceptions are bad" as a blanket rule.
Further Reading