RizTech Academy logo
RizTech Academy
Modules and ToolingLesson 3 of 325 min

Debugging in devtools properly

Module 1 said to take console.log out before shipping and promised a better tool. Here it is. The difference is not convenience: a breakpoint lets you look at everything at a moment in time, where a log shows you only what you thought to ask for before you knew what was wrong.

Why logging runs out

function total(orders) {
  console.log('orders', orders);
  const paid = orders.filter((o) => o.paid);
  console.log('paid', paid);
  const sum = paid.reduce((t, o) => t + o.plates * 80, 0);
  console.log('sum', sum);
  return sum;
}

To answer one question you edited the function, reloaded, read the output, guessed again, and edited it again. Four cycles, and now you have to remove six lines.

A breakpoint answers all of those at once — plus the questions you had not thought of — without changing the file.

console.log is still fine for a quick check or a value you want on every run. It stops being the right tool the moment you are exploring.

Setting a breakpoint

Open Sources in devtools, find your file, click a line number. Reload or trigger the code. Execution stops before that line runs, and the page freezes with everything still in place.

Now, without touching your code:

Scope shows every variable in reach — local, closure and global. This is module 3's scope chain, made visible, and it is the fastest way to understand a closure you are not sure about.

Call stack shows how you got here. Click any frame to inspect its variables — invaluable when a function is called from three places and misbehaves from one.

Console still works, and runs in the paused scope. Type a local variable name and see it. Try the expression you are unsure about. This is the single most useful thing in the panel.

Watch keeps an expression on screen, re-evaluated at every step.

Stepping

Control Does Shortcut
Resume Carry on until the next breakpoint F8
Step over Run the next line, not into calls F10
Step into Go into the function being called F11
Step out Finish this function, return to the caller Shift + F11

Step over is the one you want most: walk down a function watching values change. Step into when you reach the call you actually suspect.

The breakpoints worth knowing

A conditional breakpoint — right-click a line number, Add conditional breakpoint:

order.id === 47

Stops only when that is true. This is the one that changes how you debug. Three thousand rows and the bug is on one of them: the condition finds it, where stepping would take all afternoon.

A logpoint prints an expression without stopping and without editing the file — right-click, Add logpoint. A console.log you cannot forget to remove.

debugger in your code stops there when devtools is open:

function total(orders) {
  debugger;
  return orders.reduce((t, o) => t + o.plates * 80, 0);
}

Useful when the code is hard to find in Sources — inside a template string, or generated. It is still a line in your file, so it must come out before you ship. It is the only one here with that problem.

Pause on exceptions — the pause-symbol button at the top of Sources. The debugger stops at the moment an error is thrown, with the scope intact. Tick "Pause on caught exceptions" too when an error is being swallowed by a try/catch you did not write.

For an error you cannot reproduce on demand, this is usually the fastest route to it.

Event listener breakpoints — pause on any click, or any fetch. Good for "something happens when I click and I cannot find the code".

Debugging async code

Module 8's code is where stepping feels strange: step over an await and you land somewhere unrelated, because the thread went off to do other work.

Two things help.

The call stack shows async frames. Devtools stitches the chain together, so you can see which await you came from — not just the microtask that resumed you.

Break on the await, not inside the promise. Put the breakpoint on the line after the await and let it run; you get the resolved value in scope with the stack intact.

For a failing request, the Network panel is faster than any breakpoint. It tells you the status, the headers, the response body and the timing. Module 8's whole "three outcomes" distinction — worked, server said no, could not ask — is visible there in one glance, and it is where you confirm whether the problem is yours or the server's.

Reading an error properly

Before any of this, read the error. Module 1's three parts, now with the stack:

Uncaught TypeError: Cannot read properties of undefined (reading 'plates')
    at total (orders.js:12:34)
    at render (app.js:45:18)
    at HTMLButtonElement.<anonymous> (app.js:60:5)

Read the stack bottom-up to see how you got here, top-down to find where it broke. The top frame is where it threw — orders.js line 12. The frames below are who called it.

orders.js:12:34 is a link. Click it and Sources opens at that character.

The stack often points at a library, not your code. Scroll down to the first frame that is yours — that is usually where the wrong value came from. A library throwing means you handed it something it did not expect.

Source maps

Built or minified code is unreadable:

function t(e){return e.reduce((t,n)=>t+n.p*80,0)}

A source map lets devtools show your original file while running the built one. Bundlers generate them, and devtools uses them automatically.

If you are looking at minified code in Sources, source maps are missing or off — check the bundler's config rather than trying to read it. Debugging minified code by hand is a waste of an afternoon.

Other panels worth a visit

Panel For
Network Requests, status, payloads, timing. Throttle to Slow 3G
Application localStorage, cookies — module 7
Elements The live DOM, and what your code actually produced
Performance Why something is slow. Record, then look for long tasks
Lighthouse An audit of performance and accessibility

Throttling is the one to make a habit. Set Slow 3G and use your own page. Everything about your loading states becomes obvious, and your users in India on mobile data are the reason.

A method

Tools do not debug; you do. The habit that works:

Reproduce it reliably first. A bug you cannot trigger on demand cannot be confirmed fixed. This step is skipped constantly and costs the most time.

Read the error and the stack. Often enough on its own.

Form one specific guess. Not "something is wrong with the total" but "I think orders is empty by the time total runs".

Check that one guess — breakpoint, watch, or the Network panel.

Change one thing. Changing three and finding it works leaves you not knowing why, which means it will come back.

Then ask why it happened. A missing await is a fix; noticing that three other functions have the same shape is the thing that pays.

Check your work

A breakpoint shows every variable at a moment; a log shows one value you thought to ask for in advance.

The Console runs in the paused scope, so you can evaluate local variables and test expressions there.

Step over runs the next line without entering calls; step into enters the call; step out finishes the current function.

A conditional breakpoint stops only when an expression is true, which is how you find one bad row in three thousand.

A logpoint prints without stopping and without editing the file. debugger is a real line that must be removed before shipping.

Pause on exceptions stops where an error is thrown, with scope intact — including caught ones, if you tick that box.

For a failed request the Network panel beats a breakpoint, because it shows status, body and timing together.

Read a stack trace top-down for where it broke, bottom-up for how you got there, and find the first frame that is your own code.

Minified code in Sources means source maps are missing.

Practice

  1. Take a function with a bug and debug it twice — once with console.log, once with a breakpoint. Time both.
  2. Pause inside a function and read the Scope panel. Find a local, a closure variable and a global.
  3. Use the console while paused. Evaluate a local variable and then an expression you are unsure about.
  4. Step over a whole function, then run it again stepping into the first call.
  5. Click through the call stack and confirm the variables change with the frame.
  6. Loop over fifty items and set a conditional breakpoint that stops only on number 37.
  7. Replace a console.log with a logpoint and confirm you get the output with no code change.
  8. Add a debugger statement, use it, then remove it. Note that steps 6 and 7 did not need removing.
  9. Turn on Pause on exceptions and trigger an error. Then wrap that code in try/catch and confirm you need "pause on caught exceptions" to stop there.
  10. Break on the line after an await in your module 8 page and inspect the resolved value.
  11. Open Network, throttle to Slow 3G, and reload your orders page. Watch the loading state you wrote in module 8 actually do its job.
  12. Harder. Take the orders page, introduce three bugs without looking — a missing await, a wrong property name, and an off-by-one — then find each using a different tool: the stack trace, a conditional breakpoint, and the Network panel. Write one sentence per bug about which tool would have been slowest.

That is module nine. Your code can be split across files, use packages other people wrote, and be debugged with something better than guessing. That is the toolchain, and there is not much more to it than this at the level you need now.

Next: the capstone. One project, built from nothing to deployed, using every module in this course.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship