This article clarifies the crucial distinction between errors and exceptions in software systems, arguing that errors are expected, recoverable outcomes while exceptions signify unexpected, unrecoverable system invariants violations. Properly distinguishing these impacts system reliability, observability, API design, and resilience in distributed environments. Adhering to this principle leads to more robust, maintainable, and predictable software architectures.
Read original on Dev.to #systemdesignIn building resilient systems, a core principle is understanding how to handle unexpected situations. This article emphasizes that "errors" and "exceptions" serve fundamentally different purposes, and conflating them can lead to significant architectural and operational problems. Errors are expected, recoverable outcomes that are part of normal system behavior, such as a user providing invalid input or a resource not being found. Exceptions are unexpected, unrecoverable violations of system invariants or assumptions, indicating a truly exceptional, often fatal, state.
Practical Rule for System Architects
If the caller can handle it, it's an error. If the system cannot safely proceed, it's an exception. This rule guides the design of resilient systems and clear API contracts.
Architecturally, errors should be handled gracefully through explicit returns, discriminated unions, or `Result` types, allowing upstream components to take corrective actions. Exceptions, conversely, should ideally lead to fast failures, immediate logging (e.g., to an error tracking system), and potentially system restarts or circuit breaker activations. This strategy ensures that critical system invariants are preserved and that truly anomalous situations are brought to immediate attention rather than being silently suppressed or mishandled.
// ✅ Modeling errors explicitly in TypeScript for expected outcomes
function getUser(id: string): User | null {
return db.find(id) ?? null
}
// ❌ Using exceptions for normal outcomes leads to fragile APIs
function getUserThrowing(id: string): User {
const user = db.find(id)
if (!user) throw new Error("User not found")
return user
}