Errors as Values
Errors are part of every software project. Most developers handle them using try-catch
, but that’s not the only way. Especially in strongly typed languages like TypeScript.
One alternative is treating errors as values. In this post, I will explain what that means, why it works well in TypeScript, and when to use it.
What is Errors as Values?
Returning errors as values means your function doesn’t throw. It returns something that tells you whether it worked or failed.
In TypeScript, this often means using union types to describe both success and failure cases in the return type.
Here’s an example of a paginated API response using this pattern:
type PaginatedRes<T> =
| {
success: true;
data: T[];
totalPages: number;
}
| {
success: false;
};
If the request is successful, you get the data
and totalPages
. If not, you get success: false. No need to throw or catch anything.
The error is part of the return value.
Advantages of Errors as Values
Type-Safety
When you return errors as values, TypeScript can help you catch mistakes at build time.
You define exactly what a failure looks like, so you are forced to handle it. No surprises at runtime.
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);
Now result
forces you to check if the call succeeded. TypeScript makes sure you don’t forget the failure case.
Readability
Returning errors as values also makes your code easier to read.
You don’t have to dig through the function body to see how errors are handled. It’s right there in the return type.
const result = getPaginatedData(2);
if (result.success) {
console.log(result.data);
} else {
console.log("Something went wrong");
}
No try-catch
, no guessing. Just a clear success check based on the return value.
You don’t have to use the errors as values approach everywhere. But in TypeScript, it’s often the better choice. It helps you write safer code and avoid surprises at runtime.