Which patterns prevent bugs in JavaScript code?
Four patterns do most of the work, and they are all about moving the checks to the right place.
Guard clauses. Validate the arguments at the top of the function and bail out early, so the body of the function only ever deals with valid data. The alternative is to wrap the whole body in a conditional, which pushes the real code one indentation level deeper for no benefit.
Defaults for everything optional. A default parameter turns a missing argument into a defined value at the boundary, so the rest of the function never has to consider the empty case.
Immutable updates. Building a new value instead of changing an existing one means the old value is still correct afterwards. That is what makes undo, time travel and change detection possible, and it removes a whole family of aliasing bugs.
Frozen shared configuration. If several parts of the program read the same settings object, freezing it turns an accidental mutation into an immediate error in strict mode rather than a strange value three functions later.
// 1. Guard clauses at the top of a function.
const parsePort = (value) => {
if (typeof value !== "number") throw new TypeError("port must be a number");
if (!Number.isInteger(value) || value < 1 || value > 65535) {
throw new RangeError("port must be between 1 and 65535");
}
return value;
};
// 2. Provide defaults for anything optional.
const connect = (host, port = 5432, timeout = 5000) => ({ host, port, timeout });
// 3. Freeze shared configuration so it cannot be edited by accident.
const settings = Object.freeze({ theme: "dark", locale: "en-GB" });
// 4. Prefer immutable updates.
const addItem = (list, item) => [...list, item];
console.log(parsePort(5432)); // 5432
console.log(connect("db.local")); // { host: "db.local", port: 5432, timeout: 5000 }
console.log(Object.isFrozen(settings)); // true
console.log(addItem([1, 2], 3)); // [1, 2, 3]Two more that are worth mentioning. Validating at the boundary of a system, once, means the rest of the code can trust its inputs. And keeping the shapes of your data simple, with consistent property names across the code base, removes the class of bug where two functions disagree about what a property is called.