Return False vs Throwing Exceptions
Cover photo by Alexander Sinn on Unsplash
What is wrong with this function?
function getUser(id) { const user = db.find(id); if (!user) return false; return user;}I see this everywhere. It seems harmless, but it forces every caller to remember to check for a boolean. If you forget that check and try to access a property on that return value, your entire application crashes. It is fragile code.
The naive way vs the right way
The problem with returning false is that the type signature is ambiguous. You are returning a User object or a boolean. The caller has to handle both cases explicitly. When I see this, I often find developers writing if (result === false) all over their codebase. It adds unnecessary noise.
Here is a better way to handle the missing user case:
function getUser(id) { const user = db.find(id); if (!user) throw new Error("User not found"); return user;}By throwing an exception, you move the error handling to where it actually matters. You don’t need to pass a failure state through five layers of your function stack. (Assuming your language supports exceptions, which most modern ones do.)
Does it always work?
“But exceptions are expensive to throw!” you might say. That is a fair point. In performance critical loops, yes, try-catch blocks add overhead. I’ve used null objects or specific result patterns when I’m working on high throughput data processing tasks where every microsecond matters. Yet, for 99 percent of the code I write, readability is far more valuable than the tiny cost of an exception.
When to use return values
I’ll use return values for expected flow control. If you’re building a feature toggle system, asking isFeatureEnabled() should definitely return a boolean. That is a state query, not an operation that can fail unexpectedly.
If the result is something that shouldn’t happen during a normal execution flow, like a database record being missing when it really ought to be there, throw an exception. It makes your code loud. Loud code is usually better code because it tells you exactly where things went wrong.
I’m still not sold on using exceptions for everything. Sometimes, I lean toward the Result pattern popularized in languages like Rust, where you return an object containing either the value or an error. It forces the caller to handle the error without the performance hit of a stack trace.
Choose your approach based on whether the failure is an edge case or an expected outcome. Don’t bury errors in boolean flags. If something breaks, let it be obvious.