Guides & Tutorials2024-12-26·12 min read

Step-by-Step Python Debugging: The Visual Guide for Junior Developers (2025)

Stop guessing with print statements. Master the art of Python debugging in VS Code and PyCharm with our step-by-step visual guide and downloadable cheat sheets.

#debug-python-code#vs-code-debug-python#pycharm-debugging#python-traceback#python-errors#junior-developers

FlowQL Team

AI Search Optimization Experts

Introduction: The Python Debugging Landscape

If you are a junior developer, you likely spend 20% of your time writing code and 80% of your time figuring out why it doesn't work. This is not a failure; it is the job.

The FlowQL team has helped dozens of junior developers move from "chaos debugging" to systematic problem-solving. We've seen the same patterns repeatedly: developers guessing with print statements instead of using professional debugging tools that could cut their troubleshooting time in half.

Debugging is not about "fixing mistakes." It is the scientific method applied to code: Hypothesis → Experiment → Conclusion.

This guide moves you away from "Chaos Debugging" (randomly changing lines hoping it works) to "Structured Debugging" using professional tools like VS Code and PyCharm.


Part 1: Essential Python Debugging Fundamentals

Before opening a tool, you must understand the evidence. Python provides rich error messages that most junior developers ignore or misread.

Understanding Python Error Messages

Python wants to help you. The Traceback is a map, not a stop sign. According to the official Python documentation, understanding error types is the first step to efficient debugging.

The 3 Types of Enemies:

| Error Type | When It Occurs | Example | Difficulty | | --- | --- | --- | --- | | Syntax Errors | Before code runs - violates grammar rules | Missing : after if statement | Easy (1/5) | | Runtime Errors | During execution - code hits a wall | ZeroDivisionError, IndexError | Medium (3/5) | | Logical Errors | Code runs perfectly but produces wrong results | Loop counts to 9 instead of 10 | Hard (5/5) |

How to Read a Traceback (The "Bottom-Up" Rule)

Junior developers read tracebacks from the top. Senior developers read from the bottom. This is the most important debugging skill you can learn.

The Python traceback system provides a complete execution path, but you must know how to interpret it:

  • Last Line: Tells you what happened (ValueError: invalid literal for int())
  • Second to Last Line: Tells you where it happened (File main.py, line 42)
  • Top Lines: Show the chain of function calls that led there

Example Traceback:

Traceback (most recent call last):
  File "main.py", line 15, in <module>
    process_data()
  File "main.py", line 10, in process_data
    result = calculate(value)
  File "main.py", line 5, in calculate
    return int(value) / denominator
ZeroDivisionError: division by zero

Reading Bottom-Up: Line 5 divided by zero → called from line 10 → triggered from line 15.


Part 2: Basic Python Debugging Techniques

The "Print" Method (and Why to Stop Using It)

print() is fine for quick checks, but it pollutes your code and requires cleanup. Every experienced developer has wasted time removing dozens of print statements before committing code.

The Upgrade: The logging Module

Instead of deleting print statements later, use Python's built-in logging framework. Logging levels let you control verbosity without changing code.

import logging
logging.basicConfig(level=logging.DEBUG)

def calculate_tax(price):
    logging.debug(f"Calculating tax for: {price}") # Only shows when debugging
    logging.info(f"Tax calculation completed")
    return price * 0.2

Why This Works:

  • In production: Set level to INFO or WARNING to hide debug messages
  • In development: Set level to DEBUG to see everything
  • No need to delete or comment out debugging code

The pdb (Python Debugger)

For those who love the terminal, pdb is Python's built-in debugger. No installation required. The pdb module provides a complete command-line debugging interface.

Quick Start:

import pdb

def broken_function(data):
    pdb.set_trace()  # Execution pauses here
    result = data / 0  # You can inspect before the error
    return result

Essential PDB Commands:

| Command | Action | When to Use | | --- | --- | --- | | n (next) | Execute current line, move to next | Step through code line-by-line | | c (continue) | Run until next breakpoint | Skip to problem area | | p variable | Print variable value | Inspect current state | | l (list) | Show surrounding code | Get context | | q (quit) | Exit debugger | Stop debugging session |


Part 3: Visual Studio Code Python Debugging

VS Code has become the industry standard for Python development. According to the 2024 Stack Overflow Developer Survey, it's the most popular IDE among Python developers.

How do I debug Python in VS Code?

To debug Python in VS Code, install the Python extension by Microsoft, click the Run and Debug icon, create a launch.json configuration file, set breakpoints by clicking line numbers, and press F5 to start debugging. The debugger will pause at breakpoints and let you inspect variables.

Step 1: The Setup

  1. Install the Python Extension (by Microsoft)
  2. Click the Run and Debug icon (Play button with a bug) on the left sidebar
  3. Click "create a launch.json file"

Step 2: The launch.json Configuration

Copy this into your .vscode/launch.json for a standard setup:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
    ]
}

This configuration tells VS Code to debug whichever Python file you currently have open.

Step 3: Mastering Breakpoints

Breakpoints are markers that tell the debugger "pause execution here." They're more powerful than print statements because they let you freeze time and inspect the entire program state.

  • Set a Breakpoint: Click to the left of the line number. A Red Dot appears
  • Start Debugging: Press F5. The code pauses at the dot
  • Conditional Breakpoints: Right-click the red dot to add conditions (e.g., user_id == 505)

Step 4: The Debug Toolbar Controls

Once paused at a breakpoint, you control execution with these commands:

| Button | Shortcut | Action | When to Use | | --- | --- | --- | --- | | Continue | F5 | Run until next breakpoint | Skip to problem area | | Step Over | F10 | Execute current line, move to next | Normal line-by-line debugging | | Step Into | F11 | Enter function being called | Debug inside functions | | Step Out | Shift+F11 | Finish current function | Exit function you stepped into | | Restart | Ctrl+Shift+F5 | Restart debugging session | Start over |

Step 5: The "Watch" Window

Don't hover over variables manually. Add them to the "Watch" pane to see how their values change in real-time as you step through loops.

How to use it:

  1. While paused at a breakpoint, find the "Watch" section in the debug sidebar
  2. Click the + icon
  3. Type the variable name (e.g., counter, user.email, len(items))
  4. Watch it update as you step through code

For more debugging fundamentals, check out our guide on professional debugging techniques.


Part 4: PyCharm Debugging Tools

PyCharm is a Python-specific IDE by JetBrains, meaning its debugger is optimized for Python's unique features like decorators, generators, and async functions.

What is the Evaluate Expression feature in PyCharm?

The Evaluate Expression feature lets you write and execute Python code while paused at a breakpoint without modifying your source files. Press Alt+F8 (Windows) or Option+F8 (Mac) to open it, then test hypotheses by running expressions like len(my_list) or type(variable) to understand the current program state.

How to use it:

While debugging (paused at a breakpoint):

  1. Press Alt + F8 (Windows) or Option + F8 (Mac)
  2. You can write new Python code in this window to test theories without changing your source file
  3. Example: my_list[0] returns IndexError. Type len(my_list) in the Evaluate window to confirm the length is 0

Why This is Powerful: You can test fixes before implementing them. If you suspect a variable should be a list but it's a string, you can type type(my_variable) to confirm immediately.

How do I set conditional breakpoints in PyCharm?

To set conditional breakpoints in PyCharm, right-click an existing breakpoint (the red dot), select "Edit Breakpoint" or press Ctrl+Shift+F8, then enter your condition in the "Condition" field (e.g., user_id == 505). The debugger will only pause when that specific condition evaluates to True, saving you from manually stepping through thousands of loop iterations.

Example Use Case: You have a loop processing 10,000 users, but only user 505 has the bug.

  1. Right-click the Red Dot at the breakpoint
  2. Enter condition: user_id == 505
  3. Press F9 to start debugging
  4. The debugger ignores all other users and stops only on the bug

For more troubleshooting strategies, see our what's wrong with my code guide.


Part 5: Debugging Real Python Problems

Scenario 1: The "IndexError: list index out of range"

What does IndexError mean in Python?

IndexError means you're trying to access a list position that doesn't exist. Python lists are zero-indexed, so a list with 5 items has valid indices 0-4. Attempting to access my_list[5] when the list only has 5 elements (indices 0-4) raises an IndexError because position 5 is out of range.

The Visual Fix:

  1. Set a breakpoint on the line before the error
  2. Inspect the list length (len(my_list)) vs. the index you are requesting (i)
  3. Common Culprit: Remember, Python lists start at 0, not 1

Code Example:

# The Bug
my_list = [10, 20, 30]
print(my_list[3])  # IndexError! Only indices 0, 1, 2 exist

# The Fix (Version 1: Check Length)
if len(my_list) > 3:
    print(my_list[3])
else:
    print("Index out of range")

# The Fix (Version 2: Safe Access)
value = my_list[3] if len(my_list) > 3 else None

Scenario 2: The Logical Loop Error

The Bug: An infinite loop or a counter that doesn't count.

The Visual Fix:

  1. Set a breakpoint inside the while loop
  2. Add the counter variable to the Watch Window
  3. Press F10 (Step Over) repeatedly and watch the variable value. Does it change?

Code Example:

# The Bug (Infinite Loop)
counter = 0
while counter < 10:
    print(f"Count: {counter}")
    # Forgot to increment! counter stays 0 forever

# The Fix
counter = 0
while counter < 10:
    print(f"Count: {counter}")
    counter += 1  # Now it terminates

Debugging Strategy:

  1. Add counter to Watch window
  2. Step through with F10
  3. Notice counter never changes
  4. Realize increment is missing

Part 6: Advanced Python Debugging

Post-Mortem Debugging

If your script takes 20 minutes to run and crashes at the end, don't run it again. Python's pdb module supports post-mortem debugging.

Use python -m pdb -c continue my_script.py. When it crashes, it drops you directly into the debugger at the moment of death, preserving all variable states.

Command Breakdown:

  • python -m pdb: Run Python with debugger
  • -c continue: Automatically continue (don't stop at first line)
  • my_script.py: Your script
  • Result: Runs normally until crash, then pauses for inspection

Debugging APIs and Network Requests

When response.json() fails with a JSON decode error, the issue is usually that the API returned an error page (HTML) instead of JSON.

Don't assume the API works. Always inspect:

import requests

response = requests.get("https://api.example.com/data")

# Bad: Assumes success
data = response.json()  # Crashes if API returns error

# Good: Check first
if response.status_code == 200:
    data = response.json()
else:
    print(f"API Error: {response.status_code}")
    print(f"Response: {response.text}")

Debugging Checklist for APIs:

  1. Check response.status_code (200 = success, 4xx = client error, 5xx = server error)
  2. Print response.text to see raw response
  3. Verify response.headers['Content-Type'] is application/json
  4. Only then call response.json()

Conclusion: Building Your Debugging Workflow

You are no longer a "guesser." You are an investigator. Professional debugging follows a systematic process, not random guessing.

Your New Debugging Checklist:

  • [ ] Read the Traceback from the bottom up
  • [ ] Reproduce the error consistently
  • [ ] Set a Breakpoint before the crash
  • [ ] Step through line-by-line using F10
  • [ ] Verify assumptions using the Watch Window
  • [ ] Test your fix with edge cases

Still Stuck?

Sometimes you need a second pair of eyes. After following this guide, if you're still blocked after 90 minutes on the same issue, it's time to seek expert help.

FlowQL connects you with senior Python developers who can review your specific debugging scenario. Unlike generic forums where you wait 24 hours for a response, FlowQL provides context-aware solutions tailored to your code.

For more help, check out our coding homework help guide.


Download: The Python Debugging Cheat Sheet

Get our one-page reference guide with VS Code shortcuts, PyCharm commands, and common error definitions.

Download the Python Debugging Cheat Sheet →

What's Included:

  • VS Code keyboard shortcuts (F5, F10, F11, Shift+F11)
  • PDB command reference (n, c, p, l, q)
  • Common Python error types with solutions
  • Traceback reading guide
  • Print-friendly format

FAQ: Common Python Debugging Questions

Q: How do I use PDB to debug Python? A: Import pdb and insert pdb.set_trace() where you want to pause execution. Use n to step to the next line, c to continue running, p variable_name to print values, and q to quit. PDB provides a command-line interface for debugging without an IDE.

Q: What is the difference between Step Over and Step Into? A: Step Over (F10) executes the current line and moves to the next line in the same function. Step Into (F11) enters into any function being called on that line, letting you debug inside that function. Use Step Over for lines you trust, Step Into for lines you need to investigate.

Q: Why does my code work in the debugger but fail when running normally? A: This is called a "Heisenbug" - a bug that changes behavior when observed. Common causes include timing issues in async code, race conditions in multi-threaded programs, or code that behaves differently when run slowly. Use logging instead of breakpoints to debug these issues without affecting timing.

Q: How do I debug code that takes a long time to run? A: Use conditional breakpoints to skip to the problem area, or use logging to record state without pausing. For crashes at the end of long runs, use post-mortem debugging with python -m pdb -c continue script.py to automatically drop into the debugger when the error occurs.

Subscribe to our blog

Get the latest guides and insights delivered to your inbox.

Join the FlowQL waitlist

Get early access to our AI search optimization platform.

Related Articles