Production systems rarely break where the code is clever. They break where the code trusted something it should not have: a response that never came, a field that arrived empty, a webhook that fired twice. Defensive coding is the discipline of trusting nothing, and it is the least visible reason our platforms hold 99.9% uptime.
This article explains the practice in plain terms. If you run a business, it tells you what to demand from whoever builds your software. If you are technical, it is a checklist you can hold against your own codebase.
Validate at every boundary
Data crosses boundaries constantly: from the browser to your API, from a third party into your system, from one service to another. Defensive code validates at every crossing, with a schema that states exactly what is acceptable, and rejects everything else loudly. We use schema validators at the edge of every service we build, so bad data is refused at the door instead of discovered three tables deep a month later.
The business translation: data corruption is the most expensive class of bug, because by the time you notice, the bad data has spread. Validation at boundaries is how you keep it from entering at all.
Design the failure path first
Every external call your system makes will eventually fail. The gateway will time out, the insurer's API will return nonsense at 2am, the network will drop mid-request. Amateur code handles the success path and improvises the rest. Defensive code designs the failure path as carefully as the feature: timeouts on every call, retries with backoff where retrying is safe, and a clear answer for the user when the dependency stays down.
Our motor insurance platform talks to more than ten insurers, which means more than ten independent ways for a quote request to go wrong. It returns quotes in under two seconds anyway, because a slow or broken insurer degrades one row of results instead of the whole response. That behaviour was designed, and it is the difference between a hiccup and an outage.
Make duplicate work harmless
Payments, webhooks, and queues all share an awkward truth: the same message can arrive twice. Defensive systems make repetition harmless, so charging a customer, issuing a policy, or sending a notification checks whether the work was already done before doing it again. The technical term is idempotency. The business term is "nobody gets billed twice."
Let the type system carry the load
We build in strict TypeScript across our stack, which turns a whole category of production crashes into red underlines on a developer's screen. A function that might receive an empty value must say so, and every caller is forced to handle it before the code will even compile. This costs nothing at runtime and removes the single most common JavaScript failure from production entirely.
Fail fast, fail loudly
When something is genuinely wrong (a missing configuration value, a database the service cannot reach), the worst response is to limp along half-working. Defensive services check their requirements at startup and refuse to run without them. A deployment that fails in thirty seconds during a release window is an inconvenience. A service that starts successfully and quietly writes garbage for six hours is a crisis.
What to ask your development team
You do not need to read the code to audit this. Ask three questions. What happens when your main third-party dependency goes down? What happens when the same payment confirmation arrives twice? Where does the system validate incoming data? Teams that practise defensive coding answer immediately and specifically, because they made those decisions on purpose. This is also a fair question to put to us: how we build web applications describes the standards, and our case studies carry the uptime numbers those standards produce.