본문으로 건너뛰기
Previous
Next
Debugging Guide | Common Errors

Debugging Guide | Common Errors

Debugging Guide | Common Errors

이 글의 핵심

Debugging is the skill that separates good developers from great ones. This guide covers systematic debugging strategies, reading stack traces, common error patterns in JavaScript and Python, browser DevTools, Node.js debugging, and production incident response.

The Debugging Mindset

Bugs have causes. Finding the cause — not patching the symptom — is the goal.

Bad debugging:           Good debugging:
  See error              Read error message carefully
  Try random fix         Form hypothesis about cause
  Still broken           Test hypothesis (add log, isolate)
  Try another fix        Confirm root cause
  45 mins later: ???     Fix the root cause
                         Understand why it happened

The process:

  1. Read the error message (the full message, not just the last line)
  2. Find where the error occurs (file, line number)
  3. Form a hypothesis about the cause
  4. Test the hypothesis (don’t guess-fix)
  5. Fix the root cause (not the symptom)
  6. Verify the fix doesn’t break other things

Reading Stack Traces

Stack traces show the call chain when an error occurred — read from top (where it broke) to bottom (where it started):

// JavaScript stack trace
TypeError: Cannot read properties of undefined (reading 'name')
    at getUserName (utils.js:15:20)    ← where it broke
    at renderProfile (Profile.jsx:42:5)
    at renderUser (App.jsx:78:3)       ← where execution started
Error type:  TypeError (reading a property of undefined)
Message:     Cannot read properties of undefined (reading 'name')
Location:    utils.js line 15, column 20
# Python stack trace — read from bottom to top
Traceback (most recent call last):
  File "app.py", line 45, in get_user    ← started here
    return process_user(db.find(user_id))
  File "utils.py", line 12, in process_user
    return user['name'].upper()           ← broke here
KeyError: 'name'

JavaScript / TypeScript Common Errors

TypeError: Cannot read properties of undefined

// Error: user is undefined when you access user.name
const name = user.name;  // TypeError if user = undefined

// Causes:
// 1. Async data not yet loaded
// 2. API returned null/undefined
// 3. Object key typo

// Fix strategies:
// 1. Optional chaining
const name = user?.name ?? 'Unknown';

// 2. Guard clause
if (!user) return null;
const name = user.name;

// 3. Check async loading
if (isLoading) return <Spinner />;
const name = user.name;  // Safe now

Async/Await Mistakes

// ❌ Missing await — promise returned, not resolved value
async function getUser(id) {
  const user = fetch(`/api/users/${id}`);  // Missing await!
  console.log(user);  // Logs: Promise { <pending> }
  return user.name;   // undefined
}

// ✅ Correct
async function getUser(id) {
  const response = await fetch(`/api/users/${id}`);
  const user = await response.json();  // Also await the json()
  return user.name;
}

// ❌ Sequential when parallel is possible
const user = await getUser(id);
const posts = await getPosts(id);  // Waits for getUser unnecessarily

// ✅ Parallel
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);

React-Specific Errors

// Error: "Cannot update a component while rendering a different component"
// Cause: setState called during render
function BadComponent({ data }) {
  const [count, setCount] = useState(0);
  setCount(data.length);  // ❌ setState during render

  return <div>{count}</div>;
}

// Fix: use useEffect or calculate directly
function GoodComponent({ data }) {
  const count = data.length;  // ✅ calculate, don't set state
  return <div>{count}</div>;
}
// Error: "Each child in a list should have a unique key prop"
// Fix: add unique key to list items
items.map(item => (
  <ListItem key={item.id} data={item} />  // key must be unique and stable
));

// ❌ Don't use index as key if list reorders/filters
items.map((item, index) => <Item key={index} />);
// ✅ Use stable ID
items.map(item => <Item key={item.id} />);

Closure / Stale State

// ❌ Stale closure — count is always 0 in the callback
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count);  // Always 0 — captured at effect creation
      setCount(count + 1); // Bug: always sets to 1
    }, 1000);
    return () => clearInterval(timer);
  }, []);  // No deps — effect never re-runs

// ✅ Use functional update
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1);  // c is always current value
    }, 1000);
    return () => clearInterval(timer);
  }, []);
}

Python Common Errors

# IndentationError — mixing tabs and spaces
def greet(name):
    print(f"Hello, {name}")  # spaces
	return name              # tab — IndentationError!

# Fix: use consistent spaces (4 spaces standard) or configure editor

# NameError — variable used before definition or wrong scope
def calculate():
    result = total * 2  # NameError: total not defined
    total = 100         # defined after use

# AttributeError — wrong method name or None returned
user = get_user(id)     # returns None if not found
user.name               # AttributeError: 'NoneType' has no attribute 'name'

# Fix:
user = get_user(id)
if user is None:
    raise ValueError(f"User {id} not found")
user.name  # Safe

# KeyError — dictionary key doesn't exist
data = {'name': 'Alice'}
age = data['age']   # KeyError: 'age'

# Fix:
age = data.get('age', 0)        # Default value
age = data.get('age')           # Returns None if missing
if 'age' in data: age = data['age']

# TypeError — wrong argument types
def add(a: int, b: int) -> int:
    return a + b

add("1", 2)  # TypeError: can only concatenate str (not "int") to str

# Fix: type check or convert
add(int("1"), 2)

Browser DevTools

Console

// Beyond console.log — use these:
console.table(arrayOfObjects);   // Tabular view of objects
console.group('API Call');       // Collapsible group
console.log('request:', req);
console.groupEnd();
console.time('db query');        // Time measurement
await db.query(sql);
console.timeEnd('db query');     // → "db query: 45.23ms"
console.trace();                 // Print stack trace at current point
console.dir(element, { depth: 3 }); // Deep inspection of object

Network Tab

What to check when API calls fail:
  Request URL:   is it the right endpoint?
  Request method: GET/POST/etc correct?
  Request headers: Authorization header present?
  Request payload: is the body correct JSON?
  Response status: 200/401/404/500?
  Response body: what did the server actually return?

Filter: XHR/Fetch to see only API calls
Preserve log: keeps logs across page navigations

Sources / Debugger

// Set breakpoints in code
debugger;  // Browser pauses here — inspect variables, step through

// Or: click line number in Sources panel

// Step controls:
// F10 = Step over (execute next line, don't enter functions)
// F11 = Step into (enter the function on this line)
// F12 = Step out (complete current function, return to caller)
// F8  = Continue (run until next breakpoint)

Performance Tab

Record performance:
  1. Open DevTools → Performance
  2. Click Record
  3. Interact with the page
  4. Stop recording

Look for:
  Long Tasks (red) — JS blocking main thread > 50ms
  Layout thrashing — forced reflows in a loop
  Large paint events — slow rendering
  Memory leaks — heap size growing over time

Node.js Debugging

VS Code Debugger

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Node.js",
      "program": "${workspaceFolder}/src/index.ts",
      "runtimeExecutable": "ts-node",
      "env": { "NODE_ENV": "development" }
    },
    {
      "type": "node",
      "request": "attach",
      "name": "Attach to running process",
      "port": 9229
    }
  ]
}
# Start Node.js with debugger
node --inspect src/index.js      # Debugger on port 9229
node --inspect-brk src/index.js  # Break on first line

# Open chrome://inspect in Chrome → click "inspect"

Structured Logging

// Replace ad-hoc console.log with structured logs
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: process.env.NODE_ENV === 'development'
    ? { target: 'pino-pretty' }
    : undefined,
});

// Include context, not just messages
logger.info({ userId: 1, action: 'login', ip: req.ip }, 'User logged in');
logger.error({ err, userId: 1 }, 'Login failed');

// Filter in production:
// LOG_LEVEL=debug to see debug logs
// LOG_LEVEL=error to see only errors

Performance Debugging

Finding Slow Functions

// Measure execution time
console.time('expensive function');
expensiveFunction();
console.timeEnd('expensive function');

// Profile with performance API
const start = performance.now();
result = processData(largeArray);
const duration = performance.now() - start;
console.log(`processData: ${duration.toFixed(2)}ms`);

// Node.js: use --prof flag for V8 profiler
node --prof src/index.js
# Creates isolate-*.log
node --prof-process isolate-*.log > processed.txt
# Shows which functions consume most time

Memory Leaks

// Common leak: event listeners not removed
function BadComponent() {
  useEffect(() => {
    window.addEventListener('resize', handleResize);
    // Missing cleanup!
  }, []);

  return <div />;
}

// Fix: cleanup in useEffect return
function GoodComponent() {
  useEffect(() => {
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);  // Cleanup
  }, []);

  return <div />;
}

// Detect in Node.js:
node --expose-gc src/index.js
// Then: global.gc() to force GC, check heapUsed before/after

Production Debugging Checklist

# 1. Check error logs first
kubectl logs my-pod --previous    # Kubernetes
journalctl -u my-service -n 100  # systemd
tail -f /var/log/app.log | grep ERROR

# 2. Check recent deployments
git log --oneline -10             # Recent commits
git diff HEAD~1                   # What changed?

# 3. Check system resources
top / htop                        # CPU/memory
df -h                             # Disk space
netstat -tlnp | grep :8000       # Port listening?

# 4. Test the specific failing endpoint
curl -v https://api.example.com/users/1
# Check: status code, response body, headers, timing

# 5. Reproduce locally with production data/config
DATABASE_URL=prod_url node src/index.js
# Never run mutations against production data for debugging!

Rubber Duck Debugging

When stuck, explain the problem out loud (or in writing):

"The user endpoint returns 404 when the user exists in the database.
I know the database has the user because I queried it directly.
The route is registered — I can see it in the route list.
Wait — I just said it: the route IS registered. Let me check the
middleware order... oh. The auth middleware is rejecting before
the route handler runs. The token is expired."

The act of explaining forces you to state your assumptions explicitly — and usually one of those assumptions is wrong.

Related posts:

  • [Linux/macOS Terminal Cheatsheet](/en/blog/linux-mac-command-cheatsheet-for-developers/
  • [Git Beginner’s Guide](/en/blog/git-basic-usage-guide/

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Systematic approach to debugging across JavaScript, Python, and backend systems. Covers reading stack traces, common err… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • 개발자를 위한 리눅스·맥 명령어 실전 가이드 | 네트워크·파일·프로세스·디버깅
  • Git 기초 입문 [#1] — 설치·커밋·브랜치·원격 저장소 한 번에

이 글에서 다루는 키워드 (관련 검색어)

Debugging, JavaScript, Python, DevTools, Performance, Node.js, Backend 등으로 검색하시면 이 글이 도움이 됩니다.