duhan
Blog

Errors as Values

Errors are inevitable in the software development process. Traditionally, try-catch blocks are used to manage errors. However, there are alternative methods, such as the errors as values approach, especially in languages with strong type systems like TypeScript. In this article, we will discuss what returning errors as values is, the advantages of this approach, and in which cases it should be preferred.

What is Errors as Values?

Returning errors as a value means that instead of throwing an exception in case of an error, a function returns a value indicating the error. This value indicates what the error was, or whether the operation was successful. In TypeScript, this approach is implemented by including error conditions in the return types of functions.

For example, we can return the result of a paginated API call as follows:

type PaginatedRes<T> =
  | {
      success: true;
      data: T[];
      totalPages: number;
    }
  | {
      success: false;
    };

In this example, if the API call is successful, success: true, data and totalPages are returned. Otherwise success: false is returned. This is a good example of the “errors as values” approach, as we return error conditions as a value instead of discarding them.

Advantages of Errors as Values

Type-Safety

TypeScript’s allows you to explicitly specify which errors can occur if errors are returned as a value. This way, error conditions can be detected at the build time of the code and incorrect error handling practices can be avoided.

function getPaginatedData<T>(page: number): PaginatedRes<T> {
  if (page < 1) {
    return { success: false };
  }
  
  // happy path
  return {
    success: true,
    data: [],
    totalPages: 10
  };
}

const result = getPaginatedData<User>(1);

This way, you can type-safe check whether the value returned when you call the getPaginatedData() function succeeds or fails.

Readability

When errors are returned as values, the code becomes more readable. Instead of having to read the entire code to understand how errors are handled, you can easily see the error conditions by looking at the return type of the function.

const result = getPaginatedData(2);

if (result.success) {
  console.log(result.data);
} else {
  console.log('Something went wrong');
}

Compared to the try-catch block, the code is more readable and easier to understand.


You don’t have to use the errors as values approach in every situation. However, it is a good practice to use this approach in TypeScript. This way, you can handle errors more effectively and avoid unexpected errors in your code.