Error Codes
In short: A numeric or textual value that unambiguously identifies a specific type of error — instead of an exception, a function simply returns a special return value that means “error”.
In more detail: Error codes were common especially in older or systems-level languages (e.g. return value -1 or null on failure) and require the caller to manually check the return value every time — if forgotten, the error continues unnoticed. Modern, higher-level languages therefore mostly prefer exceptions, which can’t be accidentally ignored.
In Depth
int open_file(char* path) {
// returns a positive file handle on success, -1 on error
if (!exists(path)) return -1;
// ...
}
int handle = open_file("config.txt");
if (handle == -1) {
// handle the error - BUT: what if this check is forgotten?
}The fundamental problem with error codes: they syntactically look exactly like any other return value. Nothing forces the caller to actually check the return value — if the if check is forgotten, the program simply continues with the error value (e.g. -1 as a supposed file handle), as if nothing had happened, until the error causes a mysterious follow-on failure somewhere else entirely in the code, far from the actual cause. This “silently continuing with invalid data” is the main criticism of error codes.
A second practical problem: the value range for “error” and “valid result” has to be cleanly separable. For a function that returns an arbitrary integer (where negative values are also valid results), -1 can no longer be uniquely marked as “error” without colliding with a genuine, valid result value — then additional mechanisms are needed (e.g. a separate “has error?” output parameter).
Exceptions solve both problems structurally: an error actively interrupts the normal control flow instead of disguising itself as an inconspicuous return value, and it can carry any amount of additional context (an error message, the stack trace, the exact error type as its own type), with no collision with the function’s actual return value range.
Still, error codes aren’t completely outdated: in very performance-critical, systems-level code (operating system kernels, real-time systems), they’re sometimes deliberately preferred, because throwing and catching exceptions has a measurable runtime overhead in some languages. Many modern languages (Go, Rust) have also deliberately chosen explicit error return values again over classic exceptions — but with language features (e.g. Rust’s Result type) that prevent the “accidental ignoring” classic error codes are accused of.
See also: Exceptions, Errors