Technical note

Stop Using Option to Silence the Compiler

Not every 'maybe missing' value belongs in an optional type. A mental model for deciding when to reach for Option — by asking whether the uncertainty comes from reality, workflow, or implementation.

Software engineering is not writing code. It’s making good engineering decisions.

Modern programming languages have largely solved the “null problem.”

TypeScript has optional properties and undefined. Python has Optional. Java has Optional. Kotlin has nullable types. Rust has Option. Swift has Optional.

One mistake I see repeatedly is treating every uncertainty the same.

The compiler says a value might be missing.

The API doesn’t always return a field.

The database allows NULL.

Someone immediately reaches for Option.

But not every uncertainty should be modeled as an optional value.

Before writing ?, ask yourself a more important question:

Where does this uncertainty come from?

In my experience, uncertainty usually comes from one of three places.

1. Reality ✅

Sometimes the business genuinely allows a value to be absent.

interface Customer {
    middleName?: string;
}

Some customers simply don’t have a middle name.

The absence itself carries business meaning.

Other examples include:

  • Apartment number
  • Promotion code
  • Passport expiry (if the customer doesn’t have a passport)
  • Date of death

These are perfect candidates for optional values because the uncertainty exists in reality—not in your software.


2. Workflow ❌

Sometimes the value isn’t missing.

It simply hasn’t reached its final state yet.

Many developers write:

interface User {
    verified?: boolean;
}

But what does undefined actually mean?

  • Verification hasn’t started?
  • Verification is pending?
  • Verification failed?
  • The verification service is unavailable?

None of these mean “missing.”

They’re different stages of a business process.

A better model is:

enum VerificationStatus {
    Pending,
    Approved,
    Rejected
}

interface User {
    verificationStatus: VerificationStatus;
}

The workflow is now explicit, and every state has a clear business meaning.


3. Implementation ❌

This is where optional values are abused most often.

Examples include:

  • The compiler complains.
  • The database column allows NULL.
  • The API omitted the field.
  • Legacy code sometimes returns undefined.
  • Refactoring would be difficult.

These aren’t business concepts.

They’re implementation details.

Don’t let implementation details leak into your domain model.

Your domain model should describe the business, not the limitations of your technology.


A Simple Mental Model

When you see uncertainty, don’t ask:

“Should I use Option?”

Instead, ask:

“Is this uncertainty coming from Reality, Workflow, or Implementation?”

Only Reality belongs in an optional type.

Workflow deserves explicit states.

Implementation deserves better architecture.