# You’re using exceptions incorrectly

> Exceptions are not meant to handle predictable errors. Discover the Result type, an alternative to try/catch.

- Author: Mathieu Eveillard
- Published: 2025-12-17
- Canonical: https://www.mathieueveillard.com/blog/result-monad/
- Translation (fr): https://www.mathieueveillard.com/fr/blog/result-monad.md

![You’re using exceptions incorrectly](https://www.mathieueveillard.com/blog/2025-12-17-result-monad/result-monad.webp)

You might be tempted to write this:

```ts
const divide = (a: number, b: number): number => {
  if (b === 0) {
    throw Error("Division by 0.");
  }
  return a / b;
};
```

Although simple, this function is very problematic. I see at least three issues:

1. This function isn’t “honest.” In fact, nothing in its signature `(a: number, b: number) => number` indicates that it can throw an exception—that is, produce a _side effect_. You have to read the implementation to find out… thanks, TypeScript 😉

2. On the other hand, if this function is not just a pure utility function outside of any business context, we might want to give more meaning to the parameters `a` and `b`, and encapsulate the primitives. If `b` is no longer of type `number` but `Denominator`, it would likely be up to that type to prevent the creation of a zero denominator.

3. But, more fundamentally, error handling poses a [semantic](https://www.mathieueveillard.com/blog/erreur-vs-exception) problem. The edge case of division by 0 is entirely predictable. You therefore have no reason to throw an exception to handle it—a mechanism that is supposed to remain… exceptional. Yes, throwing an exception means acknowledging that your application is in a state of failure (PLS) and can no longer handle the situation.

In other words, you want to communicate with the caller—possibly the end user—so that they can adjust their behavior. Whereas with an exception, you’re talking to the developers, telling them there’s a bug in the application and that they need to fix it as soon as possible, please!

So you’ll need to find another way to handle this type of error.

What we want is a type—let’s call it `Result`—that allows us to represent both the nominal case, where division can take place (`Ok`), and the boundary case where division cannot be performed because `b` is zero (`Ko`).

Furthermore, we want this type to be useful by exposing a `bind` function that will allow us to:

- In the case of passing by value (`Ok`), performing further processing based on the result of the division;
- In the case where the check fails (`Ko`), propagate the error, since there is nothing else to do.

This is what is called **polymorphism**: the implementations `ok()` and `ko()` both satisfy the contract defined by the `Result` type, but each in its own way, since in one case we have a value, and in the other, an error.

Gosh, this is interesting!

In fact, we can now chain instructions and write `divide(1, 2).bind(square)` as well as `divide(1, 0).bind(square)`. Any errors no longer cause the program to fail; the function `divide: (a: number, b: number) => Result<number, DivisionByZeroError>` clearly states what it does, and everyone is happy.

All that’s left is to add a `finally` method to produce side effects at the end of the chain, which, in an HTTP controller, for example, would look like this:

- `Ok` -> code 200
- `Ko` -> code 422 ([Unprocessable Content](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/422))

“What about asynchronous operations?” you might ask. This case requires a bit more work, but the idea remains simple: compose `Result` and `Promise` to manipulate a `Promise<Result>` type and no longer use `Promise.reject`, since `Promise.resolve` manipulates a `Result` type that _already_ represents the passing and failing cases.

You can add these few lines of code to your codebase; they have the merit of being simple and allow everyone to understand exactly what’s happening. Or you can use a code library—the go-to choice in the TypeScript world being [Neverthrow](https://github.com/supermacro/neverthrow).

In any case, congratulations—you’ve created your first monad!
