Breakpoints and the Network Tab
console.log only tells you what you thought to print, after the fact - a value wrong mid-function, a loop
misbehaving on iteration nine, and you're stuck in a log-refresh-read loop. This phase covers two better
tools: Sources, for pausing code instead of guessing what to print, and Network, for the single most
common frontend bug class - "the data won't load."
Breakpoints: freeze the code and look
A breakpoint marks a line and tells the browser: stop right before it runs and hand me control. The page runs full speed until it hits that line, then freezes - now you see every variable's real value at that instant. No printing, no guessing. You look.
Set one in Sources: open your JS file, click the line number in the left margin, and a blue marker appears.
Sources panel - checkout.js
39 function applyDiscount(cart, code) {
40 const subtotal = cart.total;
● 41 const rate = DISCOUNTS[code]; ◄── breakpoint set here
42 return subtotal - subtotal * rate;
43 }
What just happened: Next time applyDiscount runs, it pauses before line 41 - cart, subtotal, and
code already hold their real values, rate isn't computed yet. You're standing inside the function, mid-run.
When paused, DevTools shows three things:
┌─ PAUSED on checkout.js:41 ──────────────────────────────────────┐
│ │
│ SCOPE (what's true right now) CALL STACK (how we got here)│
│ code = "SUMMER" ▶ applyDiscount :41 │
│ subtotal = 80 handleCheckout :120 │
│ cart = {total: 80, items: 3} onClick :12 │
│ │
│ WATCH CONTROLS │
│ DISCOUNTS[code] = undefined ▷ resume ⤼ step over │
│ subtotal * 0.2 = 16 ↓ step into ↥ step out │
└─────────────────────────────────────────────────────────────────┘
What just happened: Watch shows DISCOUNTS[code] is undefined - "SUMMER" isn't in the DISCOUNTS
object. Bug found without a single console.log: the lookup returns nothing, so the math produces garbage.
The controls you actually use
Once paused, drive the page forward one piece at a time:
- Resume (▷) - run until the next breakpoint (or the end).
- Step over (⤼) - run the current line whole, including any function it calls, stop on the next line. Use when you trust what a line calls and only want the result.
- Step into (↓) - descend into a called function and pause on its first line - follow a bug into a helper.
- Step out (↥) - finish the current function, pop back to the caller. Use when the bug wasn't in what you stepped into.
# Paused at: return subtotal - subtotal * rate (line 42, rate = undefined)
> step over
# Page resumes... returns NaN. There's the symptom: undefined rate → NaN total.
What just happened: Stepping over line 42 with rate as undefined produced NaN (80 - 80 * undefined).
Bug traced from cause (DISCOUNTS["SUMMER"] missing) to symptom (NaN price) - a complete diagnosis.
Watch expressions and the call stack
The Watch panel holds expressions you pin, re-evaluated every time the page pauses - add
DISCOUNTS[code] once and see its value at every stop, no re-typing.
The call stack is the chain of calls that got you here: onClick called handleCheckout called
applyDiscount. Click any frame to jump into that function with its variables - no re-running needed.
See Reading a Stack Trace if frames feel shaky.
💡 Why this beats scattering logs: console.log shows one guessed value; a breakpoint shows every
value, lets you step at your own pace, and answers questions you didn't anticipate.
⚠️ Gotcha - your code might be minified. In a built app, checkout.js may arrive as one unreadable line.
Check for source maps: if your build emits them (most dev setups do), DevTools shows the original source
for breakpoints. Minified soup means they aren't loading - fix that first.
The Network tab: when the data won't load
The other giant bug class isn't in your code - it's in the conversation between page and server. The Network tab records every request the page makes.
Open Network, then reload the page (it only records while open), and you get a list:
Name Status Type Size Time
─────────────────────────────────────────────────
index.html 200 doc 4.2 kB 80 ms
app.js 200 script 120 kB 40 ms
GET /api/user 200 fetch 1.1 kB 95 ms
GET /api/orders 500 fetch 612 B 210 ms ◄── red row
What just happened: One row is red: GET /api/orders came back 500 - the server errored fulfilling the
request. The order list is empty because the data never arrived, not because your code is broken.
Reading a status code at a glance
The status code tells you who's at fault, roughly:
2xx → it worked. 200 OK, 201 Created
3xx → redirect. 301, 302
4xx → YOU asked wrong. 400 bad request, 401 unauthorized,
403 forbidden, 404 not found
5xx → the SERVER broke. 500 internal error, 502, 503
What just happened: 4xx means your request was wrong (bad URL, missing auth token, malformed body);
5xx means the server fell over - one digit aims you at the right half of the system.
Click a request to see everything
Click any row and a detail pane opens with tabs - where the real answers live:
- Headers - full URL, method, status, request/response headers (confirms an auth token was actually sent).
- Payload/Request - what your code sent: did you POST the fields the server expected?
- Response/Preview - what came back: often the actual server error for a
500, or the real data shape for a200with wrong data. - Timing - how long each phase took - catches "it's not broken, it's slow."
GET /api/orders → Response tab:
{ "error": "column \"user_id\" does not exist" }
What just happened: The Response body handed you the real cause - a wrong database column name on the server. No guessing what the backend did; a bug report you can hand off with confidence.
For builders
Check Network before your own code when a feature "doesn't work" - half the time the request failed and
your code is fine. And use Preview/Response to confirm an API returns the shape your code expects: a
200 with wrong JSON keys breaks the UI as thoroughly as a 500.
Recap
- A breakpoint (Sources panel, click the line number) freezes the page before a line runs and shows you
every local value - no
console.log, no refresh-and-guess loop. - Step over / into / out drive the paused code forward; watch expressions re-check on every pause; the call stack shows how you got there.
- The Network tab records every request - reload with it open. The status code points the finger
(
4xx= your request,5xx= the server), and the Response tab usually hands you the real cause.
Next: one real "why is this broken?" bug, walked end to end across all four panels.
[
{
"q": "Why does a breakpoint generally beat scattering console.log statements?",
"choices": [
"It runs the code faster",
"It pauses the code so you can inspect every local value and ask new questions, without refreshing between guesses",
"It automatically fixes the bug it pauses on",
"It works even when JavaScript is disabled"
],
"answer": 1,
"explain": "A breakpoint freezes execution and exposes all live state at once. A console.log only shows the one value you thought to print, and each new guess costs another edit and refresh."
},
{
"q": "In the Network tab, a request shows status 500. What does that tell you?",
"choices": [
"Your request was malformed - fix the frontend",
"The page was redirected somewhere else",
"The server hit an error fulfilling the request - the problem is likely on the backend",
"The resource was not found"
],
"answer": 2,
"explain": "5xx codes mean the server broke. 4xx codes mean your request was wrong. The 500 points you at the backend, and the Response tab often contains the actual server error message."
},
{
"q": "The Network tab is empty when you open it after the page already loaded. Why?",
"choices": [
"The page made no requests at all",
"Network only records while it's open - you need to reload with it open",
"You need a paid plan to see requests",
"The requests were all cached and are never shown"
],
"answer": 1,
"explain": "The Network tab records requests as they happen. Requests that fired before you opened it (or before a reload) aren't captured, so reload the page with the tab open."
}
]
Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.
Check your understanding 3 questions
1. Why does a breakpoint generally beat scattering console.log statements?
2. In the Network tab, a request shows status 500. What does that tell you?
3. The Network tab is empty when you open it after the page already loaded. Why?