Return Code vs Exceptions: Which One is Better?

cover

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 😅.