AI can help you debug by reading error messages. But many bugs need more information first. "The page is blank" isn't enough for AI. "The APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. returns a 401 status codeWhat is status code?A three-digit number in an HTTP response that tells the client what happened: 200 means success, 404 means not found, 500 means the server broke. and the response body says 'invalid tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use.'" gives it everything it needs. This lesson covers the tools that gather that information: console.log, browser devtools, and methodical narrowingWhat is narrowing?Checking a value's type at runtime (e.g., with typeof) so TypeScript can be sure which type it is in a given code branch..
console.log: the simplest debugger
console.log is how professional developers debug most day-to-day issues. The key is knowing where to place it and what to log.
Put it right before the line that crashes or behaves unexpectedly:
async function loadProducts() {
const response = await fetch('/api/products');
const data = await response.json();
console.log('API response:', data); // ← What does the data actually look like?
const products = data.products;
console.log('Products array:', products); // ← Is this undefined? Empty?
return products.map(p => p.name); // ← This is the line that crashes
}Label your logs
Always label your logs with a descriptive string. When you have multiple logs firing, unlabeled outputs are impossible to match to their source:
// Bad - you'll see random values with no context
console.log(user);
console.log(response);
// Good - you know exactly what each value represents
console.log('Current user:', user);
console.log('API response:', response);Other useful console methods:
| Method | Use case |
|---|---|
console.log() | General-purpose value inspection |
console.table() | Arrays of objects, shows them as a table |
console.error() | Makes the output red, useful for marking your own error conditions |
console.warn() | Yellow warning, useful for non-critical issues |
console.group() / console.groupEnd() | Groups related logs together (collapsible) |
console.count('label') | Counts how many times this line executes, useful for spotting unnecessary re-renders |
console.log to every single line, creating a wall of output. Be specific: "Add a console.log before line 12 to show the value of data."Browser devtools: the three tabs you need
Three tabs in Chrome DevTools solve 90% of debugging scenarios.
The Console tab
This is where console.log output and browser-generated errors appear. Two tips that make it more powerful:
- Filter by level: Click the filter buttons (Errors, Warnings, Info) to hide noise.
- Live JavaScript execution: Type JavaScript directly into the console to inspect current state:
document.querySelectorAll('.product-card')orlocalStorage.getItem('authToken').
The Network tab
The most important tab for any app that talks to an APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses.. Open it, reproduce the bug, and you'll see every HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. request with its URL, status codeWhat is status code?A three-digit number in an HTTP response that tells the client what happened: 200 means success, 404 means not found, 500 means the server broke., and response data.
Common debugging scenarios:
Problem: "The page is blank, no data loads"
Check Network tab:
Scenario A: No request appears at all
→ Your code never calls the API. The fetch/axios call isn't executing.
Scenario B: Request appears with status 404
→ The URL is wrong. Check the endpoint path.
Scenario C: Request appears with status 401 or 403
→ Authentication problem. Token missing or expired.
Scenario D: Request appears with status 200 but wrong data shape
→ API works, but the response structure doesn't match what your code expects.
→ Click the request, go to the "Response" tab, and look at the actual JSON.
Scenario E: Request appears with status 500
→ The server is crashing. The bug is on the backend, not in your frontend code.The Elements tab
Shows the live HTML of your page. Click the arrow icon in devtools (Ctrl+Shift+C / Cmd+Shift+C), then click any element to see its HTML, CSS, and computed styles. Use it when elements are missing, styling is off, or a component isn't rendering.
The binaryWhat is binary?A ready-to-run file produced by the compiler. You can send it to any computer and it just works - no install needed. search debugging method
When you have a bug in a large function, narrow it down systematically:
- Add
console.log('CHECKPOINT 1')in the middle of the function - If it appears, the bug is after that line. If not, the bug is before it.
- Move the checkpoint to the middle of the remaining section and repeat
async function complexFunction() {
const config = loadConfig();
const users = await fetchUsers();
console.log('CHECKPOINT 1'); // ← Does this appear?
const filtered = users.filter(u => u.active);
const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name));
console.log('CHECKPOINT 2'); // ← Does this appear?
const rendered = sorted.map(u => renderCard(u));
document.getElementById('list').innerHTML = rendered.join('');
console.log('CHECKPOINT 3'); // ← Does this appear?
}If CHECKPOINT 1 appears but CHECKPOINT 2 doesn't, the bug is in the filter or sort lines. You've narrowed 10 lines to 2.
HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. status codes
You don't need to memorize all status codes, just the categories:
| Range | Category | Meaning |
|---|---|---|
| 200-299 | Success | Everything worked |
| 300-399 | Redirect | The resource moved somewhere else |
| 400-499 | Client error | Your request has a problem |
| 500-599 | Server error | The server failed to handle your request |
The codes you'll see most often:
| Code | Name | What it means for you |
|---|---|---|
| 200 | OK | Request succeeded. If data is wrong, check the response body shape. |
| 400 | Bad Request | You sent malformed data. Check your request body and parameters. |
| 401 | Unauthorized | No valid authentication. Check that you're sending the auth token. |
| 403 | Forbidden | Authenticated but not allowed. User doesn't have permission. |
| 404 | Not Found | The URL doesn't exist. Check for typos in the endpoint path. |
| 500 | Internal Server Error | The server crashed. This is a backend bug, not a frontend bug. |
Click any request in the Network tab, then check the "Response" sub-tab to see the actual data the server returned. When status is 200 but data doesn't show, compare the response shape with what your code expects (e.g., data.products vs data.items).
Putting it all together
When AI-generated code shows a blank page with no visible errors:
- Console tab: any errors? Read the error message (lesson 1)
- Network tab: are APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. calls being made? What status codes?
- No API calls?: add
console.logaround your fetch call. Is it even running? - API returns error?: read the status codeWhat is status code?A three-digit number in an HTTP response that tells the client what happened: 200 means success, 404 means not found, 500 means the server broke. and response body. Fix the request.
- API returns 200?: log the response data. Does the shape match what your component expects?
- Data looks correct?: the bug is in rendering logic. Use binaryWhat is binary?A ready-to-run file produced by the compiler. You can send it to any computer and it just works - no install needed. search logging to narrow it down.