Error Handling & Robustness

failing fast versus graceful degradation

Picture two different alarms. A smoke detector fails fast: the instant it senses smoke it shrieks and stops everything, because the cost of carrying on as if nothing is wrong is catastrophic. A dimmer light bulb degrades gracefully: as it ages it slowly gets dimmer rather than exploding, so you keep some light even as it weakens. Both are good designs - they are opposite responses to failure, and choosing the right one for a given situation is a core robustness decision.

Failing fast means: the moment something is clearly wrong, stop immediately and visibly rather than continuing on corrupt or unknown state. If an invariant is violated, if a 'this can never happen' branch is reached, if a required resource is missing at startup, the safest thing is often to halt right there - via an assertion, an abort(), or a clean error exit - so the problem is caught at its source while it is still small and diagnosable. Graceful degradation means: when a non-essential part fails, keep the whole system running with reduced functionality instead of collapsing entirely. A video call that loses bandwidth drops to audio-only rather than disconnecting; a web page whose recommendation service is down still shows the article without recommendations; a database that cannot reach a replica keeps serving from the primary. Failing safely is the umbrella idea over both: when you cannot succeed, end up in a state that does no further harm - locks released, files flushed, no half-written data, an honest error reported - rather than a state that silently corrupts.

Why it matters and the real tension: these two pull in opposite directions and you must judge case by case. Fail fast where continuing risks corruption or where a wrong answer is worse than no answer (financial calculations, memory corruption, a violated invariant inside your own code). Degrade gracefully where partial service genuinely helps the user and the failing part is non-critical (a missing thumbnail, a slow optional feature). The dangerous middle ground is 'limping on' - catching an error you do not understand and continuing as if nothing happened, which is neither failing fast nor degrading gracefully but quietly accumulating broken state until it crashes somewhere unrelated and undiagnosable. The honest caveat: 'graceful' degradation is only graceful if you deliberately designed and tested the degraded path; an untested fallback is often just a second bug waiting behind the first.

Fail fast: at startup, if the program cannot open its essential database, it should print a clear error and exit with a non-zero status, not run with no data. Degrade gracefully: if its optional metrics-reporting server is unreachable, it should log a warning and keep serving requests - metrics are nice-to-have, not essential.

Stop hard when continuing is unsafe; degrade when a non-essential part fails.

A degraded path that was never tested is not 'graceful' - it is an untested branch hiding a second bug. And 'fail fast' in production still means failing safely: release locks, flush data, report honestly. Neither strategy excuses limping on with state you do not understand.

Also called
fail fastgraceful degradationfailing safely快速失敗與優雅降級