• Null Pointer Club
  • Posts
  • The Evolution of Error Handling: From Error Codes to Exceptions to Result Types

The Evolution of Error Handling: From Error Codes to Exceptions to Result Types

A structured breakdown of the mechanisms, tradeoffs, and design lessons behind decades of evolving error-handling strategies.

In partnership with

Error handling sits at the heart of software reliability. Yet for decades, it has been one of the most debated, misunderstood, and inconsistently implemented parts of programming. Every language community has strong opinions; every codebase reveals a different philosophy; and every generation of developers inherits new techniques meant to “fix” the shortcomings of the last.

Today’s Nullpointer Club newsletter unpacks the evolution of error handling—from the days of manual error codes to the rise of exceptions, and finally to modern, type-safe Result patterns. More importantly, we’ll explore what developers should understand when choosing an approach and how language design shapes our thinking about failure.

Let’s dive in.

84% Deploy Gen AI Use Cases in Under Six Months – Real-Time Web Access Makes the Difference

Your product is only as good as the data it’s built on. Outdated, blocked, or missing web sources force your team to fix infrastructure instead of delivering new features.

Bright Data connects your AI agents to public web data in real time with reliable APIs. That means you spend less time on maintenance and more time building. No more chasing after unexpected failures or mismatches your agents get the data they need, when they need it.

Teams using Bright Data consistently deliver stable and predictable products, accelerate feature development, and unlock new opportunities with continuous, unblocked web access.

Why Error Handling Evolves

Error handling changes because our expectations of software change. Early systems needed absolute performance and low-level control. As applications grew, developers needed abstraction and readability. And in today’s distributed, async world, we need safety guarantees and predictability.

Each phase in the evolution of error handling was a response to real pain points.

1. The Era of Error Codes

Languages: C, early C++, POSIX APIs

Error codes were the original mechanism for signaling failure. A function would return an integer or sentinel value (-1, NULL, etc.), and developers manually checked it after every call.

Pros:

  • Zero runtime overhead

  • Predictable control flow

  • Portable and straightforward

Cons:

  • Easy to ignore accidentally

  • Error logic scattered everywhere

  • No standard structure — every library invented its own conventions

  • Nested “pyramid of doom” patterns

Example from C:

FILE *fp = fopen("data.txt", "r");
if (!fp) {
    return ERR_CANNOT_OPEN_FILE;
}

Manual checks keep code honest but result in significant boilerplate. Worse, a single forgotten check can cause undefined behavior.

Why developers moved on:
As codebases grew, the cognitive burden of tracking error codes became unsustainable. Errors needed structure and centralization.

2. The Rise of Exceptions

Languages: Java, Python, C#, Ruby, JavaScript

Exceptions arrived as a clean, expressive alternative. Instead of returning error values, functions could “throw” an error that bubbles up the call stack until caught.

Pros:

  • Removes error-check clutter from the main logic

  • Enables powerful stack unwinding

  • Allows for centralized handling

  • Works naturally with OOP hierarchies

Cons:

  • Invisible control flow — errors may jump across functions you didn’t expect

  • Overuse leads to unpredictable runtime behavior

  • Harder to reason about in concurrent or async code

  • Performance penalties in some languages

Example in Python:

try:
    data = read_file("data.txt")
except FileNotFoundError:
    handle_missing()

Exceptions are expressive and readable, but they broke the explicitness developers valued. This tradeoff led to the next evolution…

Why the industry moved again:
As systems became more concurrent, distributed, and performance-sensitive, developers needed structures that kept errors explicit without sacrificing ergonomics.

3. Modern Type-Safe Result Types

Languages: Rust (Result), Swift (Result), Kotlin (Result), Haskell (Either)

Functional programming inspired the next major shift: wrapping success or failure in a type that forces developers to handle both paths.

Rust example:

fn read_file() -> Result<String, io::Error> {
    std::fs::read_to_string("data.txt")
}

Pros:

  • Compiler-enforced error handling

  • No accidental ignoring

  • Composable with functional patterns (map, and_then)

  • Predictable control flow

  • Works well with async

Cons:

  • More verbose than exceptions

  • Can feel rigid for beginners

  • Requires explicit propagation (? operator)

Result types combine the transparency of error codes with the ergonomics of exceptions—without their unpredictability.

Why many new languages choose Result:

  • Ideal for systems programming

  • Works well with async and await

  • Enables safer APIs

  • Reduces runtime surprises

This is the first error-handling model explicitly designed for correctness by default.

4. What Developers Should Learn From This Evolution

A. Error handling shapes architecture

How errors propagate—manually, invisibly, or through typed structures—affects the entire design of modules and APIs.

B. The best technique depends on the domain

  • Low-level systems → error codes or Results

  • Large-scale apps → exceptions for rapid development

  • High-integrity software → Result types for safety

C. There is no perfect model

All approaches involve tradeoffs in readability, safety, performance, and control flow.

D. Explicitness always wins long-term

Hidden control flow (exceptions) can simplify small programs but complicate large ones—leading to renewed interest in more explicit patterns.

E. Error handling is part of your API, not an afterthought

A clear error strategy improves maintainability, predictability, and onboarding.

5. Where Error Handling Is Heading Next

Structured concurrency and typed async

Languages like Swift and Kotlin are merging async execution with typed results, reducing exception use even further.

Effect systems (e.g., Koka, early Scala research)

These aim to track side effects—including failure—within types themselves.

Hybrid models

Many languages now blend exceptions with Result types, letting developers pick what's appropriate.

Final Thoughts

Error handling is a window into a language’s philosophy. Whether it prioritizes performance, developer experience, or safety, the error model reveals its core values.

As modern systems scale in complexity, Result-style explicit error handling is becoming the new standard. But understanding the historical path—from error codes to exceptions to typed results—allows developers to appreciate the strengths and limitations of each model, and choose the right tool for their domain.

Error handling isn't just syntax. It's how software communicates when something goes wrong—and how gracefully it recovers.

Until next newsletter,

Team Nullpointer Club

Reply

or to participate.