Debug Like a Pro: The Art of Finding What's Broken
Here's a truth nobody tells beginners: professional developers spend a huge chunk of their time debugging.
Not because they're bad at coding—because bugs are inevitable. The difference between a frustrated beginner and a calm professional isn't the number of bugs they encounter. It's how quickly they find and fix them.
This article teaches you the detective mindset. How to read error messages (they're actually helpful once you know how). How to isolate problems. How to clear the caches that cause phantom bugs. By the end, you'll approach errors with curiosity instead of dread.
No advanced skills required. If you've ever wondered "why isn't this working?", this is for you.
Explain it three ways
When your toy stops working, you don't throw it away—you check if the batteries are in right, if something's stuck, or if it needs new batteries. Debugging is the same: checking things one by one until you find what's wrong. It's like being a detective for broken toys!
Debugging skills directly impact velocity. A developer who can systematically diagnose issues spends hours less per bug than one who guesses randomly. This training reduces mean time to resolution across the team. ROI: faster incident response, less developer frustration, more predictable timelines.
You know when I get frustrated staring at code? This is about learning to be a detective instead of getting stressed. Read the clues, check things systematically, and the answer usually reveals itself. It's like when we troubleshoot why the WiFi isn't working—check the obvious stuff first, then dig deeper.
The debugging mindset
Before we get into techniques, let's talk about attitude.
Bugs are not personal attacks. They're puzzles. The code isn't trying to frustrate you—it's doing exactly what you told it to do. The bug is the gap between what you intended and what you actually wrote.
Frustration clouds judgment. When you feel yourself getting angry, take a 5-minute break. Walk around. Get water. Come back with fresh eyes. I've solved more bugs in the first minute after a break than in the previous hour of frustrated staring.
The error message is your friend. It's literally telling you what's wrong. Most developers panic and Google immediately. Try reading the actual message first—it often contains the answer.
Reading error messages (they're trying to help)
Error messages follow a pattern:
TypeError: Cannot read properties of undefined (reading 'name')
at UserCard (src/components/UserCard.tsx:12:15)
at renderWithHooks (react-dom.development.js:14985:18)Line 1: What broke
Cannot read properties of undefined (reading 'name') = You tried to access .name on something that doesn't exist.
Line 2: Where to look
at UserCard (src/components/UserCard.tsx:12:15) = The problem is in UserCard.tsx, line 12, character 15.
The rest: The trail
This is the "stack trace"—the path the code took to get here. Look for YOUR files (not react-dom or node_modules).
💡 The translation trick
When you see an error, translate it to plain English. "Cannot read properties of undefined" becomes "I tried to use something that doesn't exist." Now you know what to look for: where is that thing supposed to come from, and why is it missing?
The debug checklist
When something breaks, work through this list in order:
| Step | Action | What you're looking for |
| ------ | -------- | ------------------------ |
| 1 | Read the full error message | What exactly does it say? What file and line? |
| 2 | Check the browser console | Any red errors or yellow warnings? |
| 3 | Check the terminal | Is the dev server showing errors? |
| 4 | Add console.log before the error | What are the actual values at that point? |
| 5 | Google the exact error message | Has someone else solved this? |
| 6 | Clear caches and restart | Could this be stale code? |
| 7 | Simplify and isolate | Can I reproduce this with less code? |
| 8 | Ask for help with context | Include error, code, and what you tried |
Most bugs are solved by steps 1-4. Seriously. Read the error, check the console, log some values.
Strategic console logging
console.log is powerful when used strategically. Random logs create noise. Strategic logs reveal truth.
Always label your logs
// Bad - you'll forget which is which
console.log(data);
console.log(user);
// Good - clear labels
console.log("API response:", data);
console.log("Current user:", user);Use console.table for arrays and objects
const users = [
{ name: "Alex", age: 28 },
{ name: "Sam", age: 32 }
];
console.table(users); // Shows a nice table in the consoleUse console.group for related logs
console.group("User Authentication");
console.log("Token:", token);
console.log("User ID:", userId);
console.log("Permissions:", permissions);
console.groupEnd();Clean up when done
Leave console.log statements in production code and you'll eventually log sensitive data. Remove them before committing, or use a debug flag:
const DEBUG = process.env.NODE_ENV === 'development';
if (DEBUG) {
console.log("Debug info:", data);
}The four common React bugs
1. "Cannot read properties of undefined"
The bug:
function UserCard({ user }) {
return <h1>{user.name}</h1>; // Crashes if user is undefined
}
// Called without props:
<UserCard />The fix:
function UserCard({ user }) {
if (!user) return <p>No user data</p>;
return <h1>{user.name}</h1>;
}
// Or with optional chaining:
<h1>{user?.name ?? "Unknown"}</h1>2. "Each child in a list should have a unique key"
The bug:
{todos.map(todo => (
<li>{todo.text}</li> // Missing key
))}The fix:
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li> // Use unique ID from data
))}⚠️ Don't use array index as key
key={'{'}index{'}'} seems to work but causes bugs when you add, remove, or reorder items. Always use a unique ID from your data. If your data doesn't have IDs, add them.
3. "Too many re-renders"
The bug:
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1); // Called during render = infinite loop
return <p>{count}</p>;
}The fix:
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1); // Called on click, not during render
return (
<button onClick={increment}>{count}</button>
);
}4. Stale state in event handlers
The bug:
const handleClick = () => {
setCount(count + 1);
setCount(count + 1); // Both use the same stale 'count' value
// Expected: +2, Actual: +1
};The fix:
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Each uses the latest value
// Now correctly adds 2
};The cache clear dance
When code should work but doesn't, stale caches are often the culprit.
| What to clear | Command/Action | What it fixes |
| --------------- | ---------------- | --------------- |
| Browser cache | Ctrl+Shift+R (Win) / Cmd+Shift+R (Mac) | Old JS/CSS still loading |
| Next.js cache | Delete `.next` folder, restart dev | Build artifacts from old code |
| Node modules | Delete `node_modules`, run `npm install` | Corrupted or outdated packages |
| npm cache | `npm cache clean --force` | Global npm issues |
| Vercel cache | Redeploy with "Clear Cache" | Production showing old version |
The nuclear option: When nothing else works:
rm -rf .next node_modules
npm install
npm run devThis takes a minute but solves most "it works on my machine" mysteries.
The isolation technique
When you can't find the bug, make the problem smaller.
Step 1: Comment out half the code
Does it still break? The bug is in the remaining half. Doesn't break? The bug is in what you commented out.
Step 2: Repeat
Keep halving until you find the exact line.
Step 3: Create a minimal reproduction
Can you reproduce the bug with just 10 lines of code? This often reveals the issue—and if you need to ask for help, a minimal example gets faster answers.
✨ The rubber duck method
Explain the problem out loud, step by step, as if teaching someone who knows nothing. A rubber duck works. A pet works. An empty chair works. The act of explaining often reveals the answer mid-sentence. It sounds silly. It works.
Quick reference: Bug symptoms and causes
| Symptom | Likely cause |
| --------- | -------------- |
| Blank page, no errors | Uncaught async error, missing return, broken import |
| Styles not applying | Class name typo, Tailwind purging, CSS specificity |
| State not updating | Mutating state directly, missing dependency array |
| Infinite loop | setState in render, missing useEffect deps |
| "X is not defined" | Import missing, typo in variable name |
| "X is not a function" | Wrong import, undefined value |
| Works locally, breaks in production | Environment variables, build-time vs runtime |
Your practice challenge
Debug this component (there are 3 bugs):
function TodoList({ todos }) {
const [filter, setFilter] = useState("all");
setFilter("active"); // Bug 1
return (
<ul>
{todos.map(todo => (
<li>{todo.text}</li> // Bug 2
))}
<button onClick={setFilter("completed")}> // Bug 3
Show Completed
</button>
</ul>
);
}Find all three bugs and explain why each one causes problems.
What's next?
Debugging is a skill that improves with practice. Every bug you solve adds to your pattern recognition. Eventually, you'll see an error and immediately know where to look.
Interactive lab available
Want to practice debugging hands-on? The companion lab lets you:
- Diagnose real bug scenarios step by step
- Practice reading error messages
- Learn the cache clear dance
- Track your bug-squashing progress
<a href="/learn-ai-lab/skill-boosters/debug-like-pro" className="text-rose-300 hover:text-rose-100 underline">→ Try the Debug Like a Pro Lab</a>