# Bug, error, or expected case: what is the business intent?

> When should you throw an exception, return an error, or model the absence of a value? The answer lies in the business intent.

- Author: Mathieu Eveillard
- Published: 2026-02-24
- Updated: 2026-02-25
- Tags: Software craft
- Canonical: https://www.mathieueveillard.com/articles/bug-erreur-cas-nominal/
- Translation (fr): https://www.mathieueveillard.com/fr/articles/bug-erreur-cas-nominal.md

![Bug, error, or expected case: what is the business intent?](https://www.mathieueveillard.com/articles/2026-02-24-bug-erreur-cas-nominal/bug-erreur-cas-nominal.webp)

We’ve seen in the past that it’s important to distinguish between two types of errors:

- Development errors, that is, bugs, for which it is justified to throw exceptions (since, by definition, we don’t know how to handle them);
- User input or infrastructure errors, which are predictable and will occur sooner or later. These errors are best handled using the `Result` monad (`type Result<U, E> = Success<U> | Error<E>`) and [Railway-Oriented Programming](https://www.mathieueveillard.com/articles/erreur-vs-exception).

At this point, **a common pitfall would be to never throw exceptions again** and to “put everything in `Result`.” That’s a bad idea, of course. So let’s add some nuance.

To better understand the topic, let’s consider a logistics context and a `ShipmentPort` that expresses the domain’s persistence requirements, in a ports-and-adapters architecture (a.k.a., [Hexagonal Architecture](https://www.mathieueveillard.com/articles/architecture-hexagonale)).

Let’s now look at these three signatures: `getRequiredShipmentById`, `getShipmentById`, and `getNextScheduledShipmentAfterDate`. We’ll see that they imply very distinct meanings from a business perspective.

1. `getRequiredShipmentById`: here, we expect the `Shipment` to exist in the database. If it does not, it means that we, as developers, have failed to respect a business invariant—it’s a bug. By definition, this is a case we do not know how to handle, so we should throw an exception (`throw`). In TypeScript, a function’s signature does not specify the exceptions that the function might throw. We therefore indicate this behavior through the function name: `Required`, or sometimes `OrThrow`.

2. `getShipmentById`: here, the `shipmentId` is derived from user input. The case where no `Shipment` matches the ID provided by the user is therefore entirely predictable and must be accounted for. This is a business error, since the application cannot function without `Shipment`, but it’s a type of error we know how to handle, using the `Error` subtype of the `Result` monad.

3. `getNextScheduledShipmentAfterDate`: here, talking about an “error” is actually a misnomer, since the absence of a shipment is part of the normal flow, for example, creating the `Shipment` if it doesn’t already exist. Note here the use of the `Either` monad (`type Either<U> = Some<U> | None`), which is a way to represent the absence of `Shipment` (neater than `Shipment | null`, which forces us to perform multiple checks). “That’s a lot of monads,” you might say, but once you’ve tried them, it’s hard to go back 🙂

One might think that the third case introduces a new type of error. But as we’ve seen, this case is actually an expected case, so there are only two error cases, those mentioned in the introduction: bugs or predictable errors.

What should we take away from all this? That **it’s the business that determines which case we’re in**:

- Does a `Shipment` have to exist at this stage of the logistics process?
- Is its absence a realistic possibility?
- Does this absence trigger a business action?

_Note also that `ShipmentId` is the encapsulation of an ID with [nominal type emulation](https://www.mathieueveillard.com/articles/typage-nominal), an approach that helps avoid many errors related to “primitive obsession.”_
