DEV Community

JSGuruJobs
JSGuruJobs

Posted on

The Debugging Toolkit Most JavaScript Developers Don't Know They Have

You know console.log. That's about 10% of what's available to you.

Here's the other 90%.

Console Methods You're Not Using

console.table turns arrays and objects into readable tables instantly:

const users = [
  { id: 1, name: 'Alice', role: 'admin' },
  { id: 2, name: 'Bob', role: 'user' },
  { id: 3, name: 'Carol', role: 'user' }
]

console.table(users)
// Formatted table with columns for id, name, role
// Immediately reveals missing fields, wrong types

console.table(users, ['name', 'role'])
// Show only specific columns
Enter fullscreen mode Exit fullscreen mode

console.trace shows how execution reached a function:

function processPayment(order) {
  console.trace('processPayment called')
  // Stack trace reveals the full call chain
  // Instantly answers "who called this and why?"
}
Enter fullscreen mode Exit fullscreen mode

console.group organizes complex flows:

async function handleCheckout(cart) {
  console.group('Checkout Flow')

  console.log('Cart items:', cart.items.length)

  console.group('Validation')
  const isValid = validateCart(cart)
  console.log('Valid:', isValid)
  console.groupEnd()

  console.group('Payment')
  const result = await chargeCard(cart.total)
  console.log('Result:', result.status)
  console.groupEnd()

  console.groupEnd()
}
// Produces collapsible nested output
// No more scrolling through 200 flat log lines
Enter fullscreen mode Exit fullscreen mode

console.time pinpoints performance bottlenecks:

console.time('api')
const data = await fetch('/api/users').then(r => r.json())
console.timeEnd('api')
// Output: api: 247.3ms

console.time('render')
renderList(data)
console.timeEnd('render')
// Output: render: 12.1ms

// Now you know the API is slow, not the render
Enter fullscreen mode Exit fullscreen mode

console.assert logs only when something is wrong:

function processUser(user) {
  console.assert(user !== null, 'User is null!', { user })
  console.assert(user.email, 'Missing email!', { userId: user.id })
  console.assert(user.role, 'Missing role!', { userId: user.id })
  // Silent when everything is fine
  // Loud when something breaks
}
Enter fullscreen mode Exit fullscreen mode

Debugger Beyond Breakpoints

Stop clicking line numbers. Use conditional and programmatic breakpoints.

Conditional breakpoints fire only when you need them:

// Right-click a breakpoint in DevTools, add condition:
userId === "abc-123" && order.total > 100

// Now it only pauses for the specific case you're investigating
// No more stepping through 500 irrelevant iterations
Enter fullscreen mode Exit fullscreen mode

Programmatic debugger with conditions:

function processOrder(order) {
  if (order.total < 0) {
    debugger // Only pauses for the suspicious case
  }
  // Normal execution continues uninterrupted
}
Enter fullscreen mode Exit fullscreen mode

logpoints instead of console.log (Chrome DevTools):

Right-click a line number → Add logpoint → Type an expression.

It logs without modifying your code. No more forgetting to remove debug statements before committing.

The Network Panel Tricks

Filter by status to find failures fast:

Type status-code:500 in the Network filter. Only failing requests show up.

Block requests to test error handling:

Right-click any request → Block request URL. Your app now behaves as if that API is down. Does your error handling actually work?

Copy as fetch for reproduction:

Right-click any request → Copy → Copy as fetch. Paste in console. Now you can replay and modify any API call without touching your code.

Throttle specific requests:

Chrome DevTools → Network → Throttling. Simulate slow 3G to test loading states and timeouts. Most "works on my machine" bugs appear instantly.

Finding React Bugs Fast

Why did this component render?

// Temporary debugging hook
function useWhyDidYouRender(name, props) {
  const prev = useRef(props)

  useEffect(() => {
    const changes = {}
    Object.entries(props).forEach(([key, val]) => {
      if (prev.current[key] !== val) {
        changes[key] = { from: prev.current[key], to: val }
      }
    })

    if (Object.keys(changes).length > 0) {
      console.log(`[${name}] re-rendered because:`, changes)
    }

    prev.current = props
  })
}

// Usage
function UserCard(props) {
  useWhyDidYouRender('UserCard', props)
  return <div>{props.name}</div>
}
Enter fullscreen mode Exit fullscreen mode

Catch the stale closure before it catches you:

// The bug everyone hits
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const id = setInterval(() => {
      console.log('count is:', count) // Always 0. Stale.
      setCount(count + 1) // Always sets to 1
    }, 1000)
    return () => clearInterval(id)
  }, [])

  // The fix
  useEffect(() => {
    const id = setInterval(() => {
      setCount(prev => {
        console.log('count is:', prev) // Always current
        return prev + 1
      })
    }, 1000)
    return () => clearInterval(id)
  }, [])
}
Enter fullscreen mode Exit fullscreen mode

Find event listener leaks:

// This creates a new listener on EVERY render
function Search() {
  useEffect(() => {
    document.addEventListener('keydown', handleKey)
    // Missing cleanup = memory leak + ghost handlers
  })

  // After 10 re-renders, handleKey fires 10 times per keypress
  // Fix: add cleanup and deps
  useEffect(() => {
    document.addEventListener('keydown', handleKey)
    return () => document.removeEventListener('keydown', handleKey)
  }, [handleKey])
}

// Verify in DevTools:
// Elements → select node → Event Listeners panel
// If you see duplicates, you have a leak
Enter fullscreen mode Exit fullscreen mode

The Git Debugging Weapons

git bisect finds exactly which commit introduced a bug:

git bisect start
git bisect bad           # Current commit is broken
git bisect good abc123   # This old commit was working

# Git checks out the middle commit
# You test: is the bug here?
git bisect good  # or  git bisect bad

# Repeat. Git binary searches through history.
# Finds the exact breaking commit in O(log n) steps.
# 1000 commits? Found in ~10 steps.
Enter fullscreen mode Exit fullscreen mode

git blame for context:

git blame src/components/UserList.jsx

# Shows who wrote each line and when
# That weird line on line 47?
# Written 2 years ago by someone who left
# Now check the commit message for context

git log -1 --format="%B" abc123
# "Hotfix: prevent crash when API returns empty array"
# Now you know WHY line 47 looks weird
Enter fullscreen mode Exit fullscreen mode

Search for when a function was changed:

git log -p -S "calculateTotal" -- "*.js"
# Shows every commit that added or removed "calculateTotal"
# Find when it was introduced, modified, or broken
Enter fullscreen mode Exit fullscreen mode

The 5 Step Process

Every bug. Every time.

1. WHAT exactly is the symptom?
   → Not "it's broken." Specific observable behavior.

2. CAN I reproduce it?
   → If not, gather more context until I can.

3. WHEN did it last work?
   → git log --oneline --since="last week" -- src/

4. WHAT are three possible explanations?
   → Force yourself to think of three before investigating.

5. WHAT is the fastest experiment to eliminate one?
   → Design a test, not a guess.
Enter fullscreen mode Exit fullscreen mode

Six hour sessions become twenty minutes.

Not talent. Process.


Full debugging framework with more patterns and examples: jsgurujobs.com

Top comments (0)