Question

Should errors be exceptions or return values?

Vault Verified
Curated Intelligence
Definitive Source
Answer

A genuine design disagreement rather than a settled question — and the honest answer is that the two approaches differ in what they make visible in the type system and what they make easy to ignore.

Exceptions. Errors are thrown and propagate up the call stack until caught.

Their advantages: the happy path stays uncluttered by error handling; errors cannot be silently ignored by omission, since an uncaught exception terminates loudly; and they propagate automatically through layers that have nothing useful to say.

Their costs: invisible control flow — any call may throw, and nothing in the signature says so, which makes reasoning about a function's behaviour genuinely difficult. Checked exceptions attempted to fix this and are widely regarded as having failed, producing boilerplate and encouraging empty catch blocks. Exceptions are also expensive on some runtimes and are frequently misused for ordinary control flow.

Return values. Errors are returned as values — a tuple, a result type, an option.

Their advantages: error paths are visible in the type signature, so a caller cannot overlook that failure is possible; handling is explicit and local; and there is no hidden non-local jump. Languages with a Result type and a propagation operator get much of the ergonomics of exceptions without the invisibility.

Their costs: verbosity, particularly without language support; and errors can be ignored by discarding the value, which is why linters and must-use annotations exist.

The distinction that resolves most arguments: separate expected failures from bugs.

Expected failures — a file missing, invalid user input, a network timeout — are part of the domain and belong in the type signature as values. Callers must decide what to do.

Bugs and unrecoverable conditions — a violated invariant, a null where one is impossible — should fail fast and loudly, which is what exceptions or panics are for. Catching these broadly is how systems continue in a corrupt state.

What matters regardless: never swallow an error silently; preserve the cause when wrapping; do not use the error channel for control flow; and make error types carry enough context to act on.

Related Questions