Return Code vs Exceptions: Which One is Better?
Alright, so this debate pops up every few months on dev subreddits and forums
Should you use return codes or exceptions for error handling?
And honestly, there’s no %100 right answer here! Both have pros/cons, and depending on the language or context, one might make more sense than the other. Let’s see...
1. Return Codes --- Said to be "Old School Way" ---
Return codes (like 0 for success, -1 for failure, etc.) are the OG method. You mostly see them everywhere in C and C++.
They’re super explicit, the function literally returns the result of the operation.
➕ Advantages of returning codes:
- You always know when something went wrong
- No hidden control flow — what you see is what you get
- Usually faster (no stack unwinding, no exception overhead)
- Easy to use in systems programming, embedded stuff, or performance-critical code
➖ Disadvantages of returning codes:
- It’s easy to forget to check the return value (and boom, silent failure 😬)
- Makes code noisy...  Everry function call followed by if (result != SUCCESS)gets annoying
- No stack trace or context unless you manually build one
For example:
try
{
	await SendEmailAsync();
}
catch (Exception e)
{
    Log.Exception(e.ToString());
	return -1;
}
Looks fine… until you forget one of those if conditions somewhere.
2. Exceptions --- The Fancy & Modern Way ---
Exceptions came in later, mostly with higher-level languages like Java, C#, and Python.
The idea is that you throw an error and handle it somewhere else.
➕ Advantages of throwing exceptions:
- Cleaner code... You can focus on the happy path and handle errors separately
- Can carry detailed info (stack traces, messages, inner exceptions...)
- Easier to handle complex error propagation
➖ Disadvantages of throwing exceptions:
- Hidden control flow — you don’t always see what might throw
- Performance hit (esp. in tight loops or low-level systems)
- Overused in some codebases (“everything throws everything”)
Example:
try
{
	await SendEmailAsync();
}
catch (Exception e)
{
	Log.Exception(e.ToString());
    throw e;
}
Way cleaner, but if SendEmailAsync() is deep in your call stack and it fails, it can be tricky to know exactly what went wrong unless you log properly.
And Which One’s Better? ⚖️
Depends on what you’re building.
- Low-level systems, drivers, real-time stuff 👉 Return codes. Performance and control matter more.
- Application-level, business logic, or high-level APIs 👉 Exceptions. Cleaner and easier to maintain.
And honestly, mixing both sometimes makes sense.
For example, you can use return codes internally and exceptions at the boundary of your API to surface meaningful errors to the user.
Conclusion
Return codes = simple, explicit, but messy.t
Exceptions = clean, powerful, but can bite you.
Use what fits your project and your team’s sanity level 😅.
 
                 
                            
Comments
Halil İbrahim Kalkan 6 hours ago
Clear explanation. One question:
You say:
For example, you can use return codes internally and exceptions at the boundary of your API to surface meaningful errors to the user.
I think it should be opposite. Return explicit error codes to users/clients (like HTTP Status Codes), but use exceptions internally. Catch the exception on the most top level (in an error handler filter or asp.net core middleware) and send a meaningfull message with a proper return code to the end user or client application.
What do you think?
Alper Ebiçoğlu 6 hours ago
Good point. I actually agree with your reasoning in the context of web APIs or apps that expose responses to clients.
What I meant was more from a library or SDK perspective, where the internal logic may prefer simple return codes (for performance) and exceptions are thrown only when crossing into higher layers like when giving an issue to an API consumer.
But yes, in application-level architectures (like ASP.NET Core), your approach is definitely cleaner. so this is my practise handling exceptions internally, mapping them to meaningful HTTP status codes, and returning structured responses to the client
So it depends the boundary we talking about