How to Debug Like a Senior Engineer
Most developers debug by adding console.logs until something makes sense. Senior engineers debug systematically. Here's the methodology — and how to make it habit.
The Console.log Trap
Here's a debugging session most developers recognize: error appears, add console.log, run again, add more console.logs, run again, add even more. Twenty minutes later you have 15 console.log statements, a messier error, and no idea what's happening. This is not debugging — it's thrashing. Systematic debugging has a structure: observe the failure, form a hypothesis, design an experiment, run the experiment, update your hypothesis. It's scientific method applied to software. Once internalized, it reduces the time spent on any bug by 50-70%.
Step 1: Read the Error Message (Actually Read It)
Most developers scan error messages rather than read them. The error message tells you: what went wrong, where it went wrong (the stack trace), and sometimes why it went wrong. The habit: read the first line of the error message. Read the stack trace from top to bottom and find the first line that references your code (not a library). That's where the problem originates. In 60% of bugs, reading the error message carefully is sufficient to identify the fix.
// Example error: TypeError: Cannot read properties of undefined (reading 'name')
// at getUserName (/src/utils/user.js:15:20)
// at renderHeader (/src/components/Header.js:8:5)
// Reading this:
// 1. TypeError: accessing .name on undefined
// 2. Location: user.js line 15
// 3. Call chain: renderHeader -> getUserName
// Hypothesis: getUserName is receiving undefined instead of a user object
// Test: check what renderHeader passes to getUserName
// Systematic debugging catches this in 2 minutes.
// Console.log thrashing might take 20.Using the Debugger (Not Just Console.log)
Every modern browser has a debugger. VS Code has a debugger. They let you pause execution at any point and inspect the exact state of every variable. The reason most developers avoid them: they seem complicated to set up. They're not. In VS Code, you create a launch.json with two options (attach to a running process or start a new one) and set a breakpoint by clicking in the gutter. In Chrome DevTools, you set a breakpoint in the Sources panel and reload the page. After you've used the debugger once, console.log debugging will feel primitive.
// VS Code debugger launch.json for Node.js:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug App",
"program": "${workspaceFolder}/src/index.js",
"env": { "NODE_ENV": "development" }
}
]
}
// Then:
// 1. Click in the gutter next to a line number to set a breakpoint
// 2. Press F5 to start debugging
// 3. When execution hits your breakpoint, it pauses
// 4. Inspect variables in the Variables panel
// 5. Step through code line by line with F10 (next line) or F11 (into function)
// 6. See the call stack on the left — click any frame to inspect its variablesRubber Duck Debugging and Why It Works
Rubber duck debugging: explain your code, line by line, to an inanimate object (or AI, or colleague, or your cat). This sounds absurd. It works for a specific neurological reason: the act of articulating your reasoning forces you to examine assumptions you've been making implicitly. 'And then this function should... wait, actually what does this function return in this case?' The moment you have to explain it to someone else, the gaps in your model become obvious. Many senior engineers know immediately what's wrong by the time they've finished describing the problem to a colleague — before the colleague says anything.
The Binary Search Approach to Bugs
When you don't know where a bug is: apply binary search. Your code runs from start to end — the bug is somewhere in that sequence. Add a log or breakpoint at the midpoint. Is the problem before or after? Narrow to the half where the problem is. Repeat. This is O(log n) bug finding instead of O(n) line-by-line searching. For a 500-line function, this is the difference between finding the bug in 3 checks vs. 500. The same principle applies at the system level: is the bug in the frontend or backend? In the database layer or the service layer? Binary partition the system space until the bug has nowhere to hide. The debugging fundamentals module at Beyond Vibe Code teaches this methodology with real buggy codebases to practice on.