Defensive Design

Your program will be used by real people: some careless, some confused, a few actively hostile. Defensive design means writing code that expects the worst — anticipating misuse, checking everything that comes in, and failing safely when something goes wrong — instead of assuming everyone will do exactly what you hoped. It is the difference between a program that works in a demo and one that survives contact with the real world.

You have already met its front line: input validation. Defensive design is the broader habit that grows out of it.

The pillars

Guard clauses: fail early, fail clearly

A cornerstone technique is the guard clause — a check at the very top of a function that rejects bad input immediately, before any real work happens. Compare a naïve function with a defensive one. Press Run:

// Naïve: trusts its input, and quietly produces nonsense on bad data. function averageNaive(scores: number[]): number { let total = 0; for (const s of scores) total += s; return total / scores.length; // empty array -> divide by zero -> nonsense } // Defensive: guard clauses reject bad input up front, with a clear failure. function average(scores: number[]): number { if (!Array.isArray(scores)) throw new Error("scores must be a list"); if (scores.length === 0) throw new Error("cannot average an empty list"); for (const s of scores) { if (typeof s !== "number" || s < 0 || s > 100) { throw new Error("each score must be a number from 0 to 100, got: " + s); } } let total = 0; for (const s of scores) total += s; return total / scores.length; } console.log("naive, empty:", averageNaive([])); // nonsense — no real average exists console.log("defensive:", average([70, 85, 90])); // 81.66… — works try { average([70, -5, 90]); // a rogue -5 is caught at the front door } catch (e) { console.log("caught:", (e as Error).message); }

The naïve version happily divides by zero for an empty list, producing a nonsense value — a bug that will surface far away, in some other file, long after the real cause. The defensive version stops the bad data at the front door and says exactly what was wrong.

Never trust, always verify

In 1996 the maiden flight of the Ariane 5 rocket self-destructed 37 seconds after launch. The cause? A number too large to fit in the variable it was being squeezed into — an unchecked conversion that a single guard clause would have caught. The undefended line of code cost around \$370 million. Defensive design isn't bureaucracy; it's the difference between a caught error and a catastrophe.