Skip to content

Debugging Tools

Effective debugging techniques for Python and frontend development.

Python Debugging

Built-in pdb

# Insert breakpoint
breakpoint()  # Python 3.7+
import pdb; pdb.set_trace()  # Older Python

# Conditional breakpoint
if user.id == 42:
    breakpoint()

pdb Commands

h(elp)          Show help
n(ext)          Execute next line
s(tep)          Step into function
c(ontinue)      Continue until next breakpoint
r(eturn)        Continue until function returns
q(uit)          Quit debugger

l(ist)          Show source code around current line
ll              Show entire function
w(here)         Show stack trace
u(p)            Move up in stack
d(own)          Move down in stack

p expr          Print expression
pp expr         Pretty print expression
a(rgs)          Show function arguments

b lineno        Set breakpoint at line
b func          Set breakpoint at function
cl(ear)         Clear breakpoints

ipdb (Enhanced pdb)

# Install
pip install ipdb

# Use
import ipdb; ipdb.set_trace()

Benefits over pdb: - Tab completion - Syntax highlighting - Better tracebacks

pudb (Visual TUI Debugger)

# Install
pip install pudb

# Run script with pudb
python -m pudb script.py

# Set breakpoint
import pudb; pudb.set_trace()

Navigation: - n — Next line - s — Step into - c — Continue - q — Quit - ? — Help

Async Debugging

import asyncio

async def debug_async():
    # Breakpoint works in async
    breakpoint()
    await some_coroutine()

# Run with debug mode
asyncio.run(debug_async(), debug=True)

Post-Mortem Debugging

# Debug after exception
import pdb

try:
    buggy_function()
except Exception:
    pdb.post_mortem()

# Or use decorator
import functools

def debug_on_error(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception:
            import pdb
            pdb.post_mortem()
    return wrapper

@debug_on_error
def buggy_function():
    pass

Remote Debugging

# Using remote-pdb
import remote_pdb
remote_pdb.set_trace(host='0.0.0.0', port=4444)

# Connect
telnet localhost 4444

pytest Debugging

Interactive Debugging

# Drop into debugger on failure
uv run pytest --pdb

# Drop into debugger at start of test
uv run pytest -s --pdb

# Run specific test with debugging
uv run pytest tests/test_api.py::test_create_user --pdb

Debugging Fixtures

import pytest

@pytest.fixture
def user(db):
    breakpoint()  # Debug fixture setup
    return create_user(db)

def test_user(user):
    breakpoint()  # Debug test
    assert user.is_active

Capturing Output

# Show print statements
uv run pytest -s

# Show local variables on failure
uv run pytest --showlocals

# Verbose output
uv run pytest -vv

Logging for Debugging

Quick Debug Logging

import logging

# Quick setup
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

def complex_function(data):
    logger.debug(f"Input: {data}")
    result = process(data)
    logger.debug(f"Output: {result}")
    return result

Structured Logging

import structlog

log = structlog.get_logger()

def process_order(order):
    log.info("processing_order", order_id=order.id, items=len(order.items))
    # ...
    log.debug("order_validated", order_id=order.id, total=order.total)

Temporary Debug Logging

# Context manager for temporary debug level
import logging
from contextlib import contextmanager

@contextmanager
def debug_logging(logger_name=None):
    logger = logging.getLogger(logger_name)
    old_level = logger.level
    logger.setLevel(logging.DEBUG)
    try:
        yield
    finally:
        logger.setLevel(old_level)

# Usage
with debug_logging("sqlalchemy.engine"):
    db.execute(query)  # SQL will be logged

Browser DevTools

Console

// Basic logging
console.log('Value:', value);
console.info('Info message');
console.warn('Warning');
console.error('Error');

// Formatted output
console.log('%c Important', 'color: red; font-weight: bold');

// Tables
console.table([{a: 1, b: 2}, {a: 3, b: 4}]);

// Groups
console.group('User Details');
console.log('Name:', user.name);
console.log('Email:', user.email);
console.groupEnd();

// Timing
console.time('fetch');
await fetch(url);
console.timeEnd('fetch');  // fetch: 123.45ms

// Stack trace
console.trace('Trace point');

// Assertions
console.assert(user.id > 0, 'User ID must be positive');

Breakpoints

Source Panel: - Click line number to set breakpoint - Right-click for conditional breakpoint - debugger; statement in code

Types: - Line breakpoints - Conditional breakpoints - DOM breakpoints (on element changes) - XHR/Fetch breakpoints - Event listener breakpoints

Network Panel

Filtering: - fetch — Only fetch requests - xhr — Only XHR requests - domain:api.example.com — By domain - -domain:cdn.example.com — Exclude domain - status-code:500 — By status code

Features: - Throttling (Slow 3G, Offline) - Preserve log across navigation - Disable cache

React DevTools

// Highlight component renders
// Components tab → Settings → Highlight updates

// Profile renders
// Profiler tab → Record → Interact → Stop

// Search components
// Components tab → Search box (Ctrl+F)

// Edit props in DevTools
// Components tab → Select component → Edit props

Vue DevTools

// Inspect component state
// Components tab → Select component → View state

// Track Vuex/Pinia state
// Vuex/Pinia tab → View mutations/actions

// Timeline
// Timeline tab → View component events

Network Debugging

curl

# Basic request
curl https://api.example.com/users

# With headers
curl -H "Authorization: Bearer token" https://api.example.com/users

# POST JSON
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"name": "test"}' \
  https://api.example.com/users

# Verbose (see headers)
curl -v https://api.example.com/users

# Only response headers
curl -I https://api.example.com/users

# Follow redirects
curl -L https://example.com

# Save to file
curl -o output.json https://api.example.com/users

# With timing
curl -w "@curl-format.txt" -o /dev/null -s https://api.example.com/users

curl-format.txt:

     time_namelookup:  %{time_namelookup}s\n
        time_connect:  %{time_connect}s\n
     time_appconnect:  %{time_appconnect}s\n
    time_pretransfer:  %{time_pretransfer}s\n
       time_redirect:  %{time_redirect}s\n
  time_starttransfer:  %{time_starttransfer}s\n
                     ----------\n
          time_total:  %{time_total}s\n

httpie

# Install
pip install httpie

# GET request
http https://api.example.com/users

# POST JSON
http POST https://api.example.com/users name=test email=test@example.com

# With auth
http -a user:pass https://api.example.com/users
http --bearer token https://api.example.com/users

# Headers
http https://api.example.com/users X-Custom-Header:value

# Form data
http -f POST https://api.example.com/login username=test password=secret

mitmproxy

Intercept and modify HTTP/HTTPS traffic.

# Install
pip install mitmproxy

# Start proxy
mitmproxy

# Or web interface
mitmweb

# Configure app to use proxy
export HTTP_PROXY=http://localhost:8080
export HTTPS_PROXY=http://localhost:8080

Memory Debugging

Python Memory Profiling

# Install
pip install memory_profiler

# Profile script
python -m memory_profiler script.py

# Profile function
from memory_profiler import profile

@profile
def memory_intensive():
    large_list = [i for i in range(1000000)]
    return sum(large_list)

Finding Memory Leaks

import tracemalloc

tracemalloc.start()

# Run suspicious code
result = memory_intensive_function()

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')

print("Top 10 memory allocations:")
for stat in top_stats[:10]:
    print(stat)

objgraph (Object Reference Graphs)

import objgraph

# Find objects
objgraph.show_most_common_types(limit=10)

# Find growth between calls
objgraph.show_growth()

# Show references to object
objgraph.show_backrefs([obj], filename='refs.png')

Performance Debugging

Python Profiling

# cProfile
python -m cProfile -s cumtime script.py

# Line profiler
pip install line_profiler
kernprof -l -v script.py
# Profile specific function
from line_profiler import profile

@profile
def slow_function():
    pass

Timing Code

import time
from contextlib import contextmanager

@contextmanager
def timer(name="Block"):
    start = time.perf_counter()
    try:
        yield
    finally:
        elapsed = time.perf_counter() - start
        print(f"{name}: {elapsed:.4f}s")

# Usage
with timer("Database query"):
    results = db.execute(query)

VS Code Debugging

Python Configuration

.vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "debugpy",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal"
    },
    {
      "name": "Python: FastAPI",
      "type": "debugpy",
      "request": "launch",
      "module": "uvicorn",
      "args": ["app.main:app", "--reload"],
      "jinja": true
    },
    {
      "name": "Python: pytest",
      "type": "debugpy",
      "request": "launch",
      "module": "pytest",
      "args": ["tests/", "-v"]
    }
  ]
}

Node.js Configuration

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "bun",
      "runtimeArgs": ["dev"],
      "skipFiles": ["<node_internals>/**"]
    }
  ]
}

Quick Debug Techniques

# 1. Print debugging (sometimes fastest)
print(f"DEBUG: {variable=}")  # Python 3.8+

# 2. Quick type check
print(f"Type: {type(obj)}, Value: {obj!r}")

# 3. Stack trace without exception
import traceback
traceback.print_stack()

# 4. Interactive exploration
import code
code.interact(local=locals())

# 5. Quick JSON dump
import json
print(json.dumps(data, indent=2, default=str))