What Is Good Error Handling, Really?


Have you ever shipped a feature, crashed production, and then stared blankly at your PC, wondering what happened? Yeah, welcome to error handling, or as a buddy of mine calls it: the art of pretending chaos is under control.
Let's get this out of the way: error handling isn’t about avoiding failure. It’s about making failure slightly less catastrophic like choosing to land your plane in a cornfield instead of a mountain.
So let’s break this thing down:
🌩 Graceful Degradation, Fail-Fast, and Resilient Design
You’ve probably heard these terms thrown around in tech meetings, but what do they mean?
Graceful Degradation
This is when your app doesn’t throw a tantrum just because one part failed. Think: Netflix is down? Okay, fine. But at least the homepage still loads and I can stare at thumbnails.
Fail-Fast
The opposite of sweeping problems under the rug. If something's wrong, make it known fast. Your API should not “kinda” work, it should either do the job or crash like a toddler denied a cookie.
Resilient Design
This is the system equivalent of having an emotionally stable friend. Something goes wrong? They have a plan. Maybe it’s retries, maybe it’s fallback logic, maybe it’s screaming silently behind the scenes. Either way, users don’t feel it.
⚖️ Expected vs Unexpected Errors
This is the difference between I knew this might happen and what fresh hell is this?
Expected errors: A user gives you garbage input. A network times out. A file isn’t found. These are normal screwups. You plan for these. You return polite messages. You log warnings. You don’t freak out.
Unexpected errors: Null pointer exceptions. Race conditions. The database is on fire. These are not things you anticipate. They deserve your panic, but also your attention. You should catch them, log them, and contain them.
Good error handling means acknowledging that both of these will happen and treating them differently. A 400 is a “try again.” A 500 is a “call for help.”
🫦 try/catch Overuse vs Strategic Error Boundaries
Let’s talk about the try/catch
addiction.
Yes, exceptions should be caught. No, that does not mean you wrap every other line in try {}
like you're bubble-wrapping your code for shipping. Catching every error and doing nothing is the same as yelling "Oops!" during a car crash and then driving off.
That’s why modern frameworks give you error boundaries—a way to catch groups of issues in a meaningful place. In frontend land (hi React), this could be wrapping a component tree. In backend services, it’s middleware that logs and responds without leaking stack traces into the void.
The goal isn’t to silence every error. It’s to localize the chaos. Keep the bug from spreading like mold in a damp basement.
So What Is Good Error Handling?
It's not just about avoiding the red screen of death. It’s about:
Knowing what can go wrong
Designing fallback behavior that keeps things usable
Failing loudly when needed, quietly when appropriate
Not catching exceptions just to whisper "shhh" into the log file
Logging like your future self will need to do forensic debugging at 3 am
Think of error handling like good parenting: you're not preventing mistakes, you're just making sure nobody dies and the house doesn’t burn down.
TL;DR
Principle | Bad Example | Good Example |
Graceful degradation | Blank screen if API fails | “We couldn’t load this” message + fallback |
Fail-fast | Silent null error three functions deep | Validate and crash early |
Expected vs Unexpected | 500 for invalid input | 400 for bad input, 500 for bugs |
try/catch usage | Catch everything, log nothing | Catch once, log meaningfully, recover, or exit |
Final thought:
Code like nothing works and design like someone will click the one button you forgot to test. Because they will.
Subscribe to my newsletter
Read articles from Walter Soto directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
