Debugging
Every developer spends a large share of their time finding out why code doesn't do what they expected. Debugging is a skill you can learn: read error messages carefully, inspect values instead of guessing, and use the browser's developer tools to pause code and look around.
Read the error message
Error messages are more helpful than they first look. Take this one:
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
at renderUser (app.js:14:28)
at loadPage (app.js:31:3)- Type —
TypeError: a value was used the wrong way. - Message — something was
undefined, and the code tried to read.namefrom it. - Stack trace — it happened in
renderUser, at line 14, column 28 ofapp.js, which was called fromloadPageat line 31.
So: on line 14, the thing before .name is undefined. Now ask why — what was supposed to set it? Clicking the file link in the console jumps straight to the line.
Common messages decoded
| Message | Usual cause |
|---|---|
X is not defined | Typo in a name, a missing import, or using a variable outside its scope |
Cannot read properties of undefined / null | A missing object: unloaded data, wrong property name, querySelector found nothing |
X is not a function | Calling something that isn't a function — typo in a method name, or wrong type of value |
Unexpected token | Syntax error: missing bracket, comma or quote nearby |
Assignment to constant variable | Reassigning a const |
Cannot access 'X' before initialization | Using a let/const before its declaration line |
Maximum call stack size exceeded | Infinite recursion — a function calling itself with no stopping condition |
Logging with purpose
console.log debugging works, but make the output informative:
const user = { id: 7, name: "Ana", roles: ["editor"] }; const cart = [{ sku: "A1", qty: 2 }]; console.log({ user, cartSize: cart.length }); console.log("roles:", JSON.stringify(user.roles));
{ user: { id: 7, name: 'Ana', roles: [ 'editor' ] }, cartSize: 1 }
roles: ["editor"]Wrapping variables in braces — console.log({ user }) — prints their names alongside their values. In the browser, console.table displays arrays of objects as a table, console.trace() prints how the current line was reached, and console.count("label") counts how often a line runs.
structuredClone(obj) or JSON.stringify(obj) to capture a snapshot.Breakpoints
A breakpoint pauses code at a line so you can inspect everything at that moment. In Chrome or Edge DevTools:
- Open DevTools (F12) and go to the Sources panel (Debugger in Firefox).
- Open your file and click a line number to set a breakpoint.
- Trigger the code (reload, click the button…). Execution pauses at that line.
- Hover over variables to see their values, or check the Scope pane.
- Step through with the toolbar: Step over (next line), Step into (enter a function call), Step out (finish the current function), Resume.
You can also pause from code with the debugger; statement, which acts as a breakpoint whenever DevTools is open:
function applyDiscount(order) { debugger; // execution pauses here when DevTools is open return order.total * (1 - order.discount); }
Other useful breakpoint types: conditional breakpoints (right-click a line number — pause only when i === 99), logpoints (log without editing the code), event listener breakpoints (pause on any click), and DOM breakpoints (pause when an element changes).
The Network panel
For anything involving fetch, the Network panel shows every request: its URL, method, status code, headers, the body you sent and the response you got back. Many "my data isn't showing" bugs turn out to be a 404, a CORS error, or a response with a different shape than expected — all visible here in seconds.
A debugging process
- Reproduce the bug reliably. Note the exact steps.
- Read the error and stack trace, if there is one.
- Form a hypothesis — "I think
itemsis empty here". - Test it with a log or breakpoint, rather than changing code and hoping.
- Narrow it down: comment out halves of the code, or use
git bisectto find which change introduced the bug. - Fix the cause, not the symptom, then check the original steps work.
Explaining the problem out loud, line by line — to a colleague or even a rubber duck — is surprisingly effective, because it forces you to check assumptions you'd otherwise skip.
Practice: find the bug
This function should return the average, but prints the wrong value. Add a console.log inside the loop to find out why, then fix it:
Show the fix
The loop starts at i = 1, skipping the first number. Arrays start at index 0, so it should be for (let i = 0; …) — or avoid indexes entirely with numbers.reduce((a, b) => a + b, 0).