Debugging is a skill you can practice, not a talent you're born with
The engineer who finds the bug in five minutes while you have been at it for an hour is not smarter — they are following a method, and you are guessing. Debugging feels like a talent because we watch the fast result and miss the process. But the process is learnable and repeatable: reproduce, isolate, hypothesise, test, repeat. Changing things at random and re-running feels like debugging and is mostly a way to spend an afternoon adding new bugs on top of the old one. The method turns a bug from a mystery into a search, and a search always terminates.
Reproduce it reliably first
You cannot fix what you cannot trigger on demand. Before touching any code, find the exact steps and inputs that make the bug happen every time — a failing test is the gold standard, because it reproduces the bug and proves the fix. A bug you can only sometimes trigger is a bug you cannot know you fixed:
// the first move: capture the bug as a reproduction — ideally a failing test
test("cart total is wrong when an item is removed then re-added", () => {
const cart = addItem(removeItem(makeCart([{ id: 1, price: 10 }]), 1), { id: 1, price: 10 });
expect(cartTotal(cart)).toBe(10); // fails today; will prove the fix tomorrow
});
Isolate by halving the space
The core technique is binary search over the code, not over random guesses. The bug lives somewhere between “input is correct” and “output is wrong” — so check the midpoint. Is the value right halfway through the pipeline? If yes, the bug is in the second half; if no, the first. Each check halves where it can be:
function pipeline(input) {
const a = step1(input);
console.log("after step1:", a); // midpoint probe: is it already wrong here?
const b = step2(a);
return step3(b);
}
// right at step1 but wrong at output → bug is in step2 or step3. Halve again.
Two or three halvings usually pin a bug to a few lines, no matter how large the codebase.
One hypothesis, one change, then verify
The discipline that separates method from flailing: form a specific hypothesis (“the total is wrong because removed items aren’t filtered”), change one thing to test it, and re-run. If it fixes it, you understood the bug; if not, you learned something and narrow again — but you never change five things at once, because then a pass tells you nothing about which change mattered, and you have added four untested edits. Keep a note of what you have ruled out so you do not loop. The whole method — reproduce reliably, isolate by halving, test one hypothesis at a time — is just making the search systematic instead of lucky, and it is entirely practisable: every bug you fix this way makes the next one faster. The event-emitter, deep-clone, and debounce-utility exercises are good practice grounds precisely because their edge cases produce subtle bugs that reward the method and punish guessing.