Coding Help Service Debugging Python March 28, 2026 12 min read 4 views

Debugging Python Code with PDB and Print Statements | Essential Guide

Master debugging Python code by strategically combining print statements for quick insights with the powerful pdb interactive debugger to efficiently find and fix errors.

1. Problem Introduction

You’ve just finished what you thought was the perfect Python assignment. The logic made sense in your head. You typed it out carefully. You hit “Run” with the confidence of someone about to submit early.

Then you see it: a SyntaxError, a NameError, or worse—no error at all, just the wrong output. Your program runs, but it’s clearly not doing what you intended. Your perfect code has betrayed you.

We’ve all been there. Debugging can feel like the most frustrating part of programming. You stare at the screen, re-read your code ten times, and still can’t figure out why x isn’t equal to 10. It’s easy to feel stuck, stupid, or just frustrated.

But here’s the secret: professional programmers spend as much time debugging as they do writing new code. The difference is, they have a systematic approach. They don’t just stare and hope. They use tools. In this guide, we’re going to teach you two of the most powerful techniques for debugging python code with pdb and print statements. By the end, you’ll move from frustrated guesser to confident code detective.

2. Why It Matters

Learning to debug effectively isn’t just about fixing today’s homework. It’s a core skill that impacts your entire programming journey.

  • Boost Your Grades: A systematic debugging process helps you fix errors logically, not randomly. You’ll spend less time frustrated and more time producing correct, functional code for your assignments, leading to better grades.
  • Build Career-Ready Skills: In the real world, code is never perfect on the first try. Employers hire developers who can troubleshoot, understand complex systems, and fix problems efficiently. Mastering debugging now gives you a massive head start.
  • Increase Your Confidence: The feeling of finally understanding why a bug appeared and fixing it is incredibly empowering. When you have a reliable process, you stop fearing errors and start seeing them as solvable puzzles. Your confidence as a programmer will skyrocket.
    Feeling stuck right now? Book a 30-minute tutoring session and get personalized help.

3. Step-by-Step Breakdown

Let’s dive into the practical steps. We’ll start with the simpler method and build up to the more powerful one.

### Step 1: The First Line of Defense - Strategic Print Statements

Before you reach for complex tools, start with the humble print() function. It’s the most accessible tool for print statements debugging. The key is to be strategic, not random.

Explanation: Sprinkle print() statements at critical points in your code to check the values of variables and confirm the flow of execution.

Why It Matters: This gives you a “snapshot” of your program’s state at specific moments. It’s like leaving breadcrumbs to see where your program has been.

Concrete Example:

Let’s say you have a simple function that’s not returning what you expect:

 

Python

def calculate_average(grades):
    total = 0
    for grade in grades:
        total += grade
    average = total / len(grades)
    return average

my_grades = [85, 92, 78, 90]
result = calculate_average(my_grades)
print(f"The result is: {result}")
# Output: The result is: 86.25  (Works fine here, but imagine it's wrong)

If the output were wrong, you’d add strategic print statements inside the function:

 

Python

def calculate_average(grades):
    print(f"1. Entering function. Grades received: {grades}") # Check input
    total = 0
    for grade in grades:
        total += grade
        print(f"2. Inside loop. Grade: {grade}, Total so far: {total}") # Track loop
    print(f"3. Loop finished. Final total: {total}") # Check total
    average = total / len(grades)
    print(f"4. Calculated average: {average}") # Check average
    return average

my_grades = [85, 92, 78, 90]
result = calculate_average(my_grades)
print(f"The result is: {result}")

Now you can see exactly how total builds and if the average calculation is correct.

 

💡 Pro Tip: Use f-strings (like print(f”Variable name: {my_var}”)) for clear, informative output. This labels your print statements so you can easily understand the output later.

 

### Step 2: Interpreting Print Statement Output

Once you have your strategic prints, you run the code and analyze the output.

Explanation: The console output tells a story. Compare what you expected to see with what actually printed. If a print statement never appears, you know the code never reached that point.

Concrete Example:

Imagine a function to find the first even number in a list:

 

Python

def find_first_even(numbers):
    for num in numbers:
        print(f"Checking: {num}") # See which numbers are being checked
        if num % 2 == 0:
            print(f"Found even: {num}") # See if/when an even is found
            return num
    print("No even found.") # Check if loop completes without finding
    return None

data = [1, 3, 5, 7, 8, 9]
result = find_first_even(data)
print(f"Result: {result}")

 

The output would be:

Plain Text

Checking: 1
Checking: 3
Checking: 5
Checking: 7
Checking: 8
Found even: 8
Result: 8

 

This confirms the function correctly skipped the odd numbers and stopped at the first even. If it had returned the wrong number, the prints would show exactly where the logic went wrong.

### Step 3: Recognizing the Limits of Print Debugging

While powerful, print debugging has its limits. When your code gets complex, you’ll hit them.

Why It Matters: Knowing when to switch tools is a sign of a maturing programmer. Print statements require you to predict the problem and add code before running it. For complex, interactive, or loop-heavy bugs, a more dynamic tool is needed.

Common Scenarios Where Print Falls Short:

  • Debugging code inside a loop that runs thousands of times (your console will be flooded).
  • Trying to understand a complex recursive function.
  • Needing to inspect the state of a program right before a crash, without adding prints everywhere.
  • Wanting to pause execution and step through code line-by-line.
    When you face these situations, it’s time to bring in the heavy artillery: pdb, the Python Debugger.

### Step 4: Introducing PDB - Your Interactive Debugger

pdb python stands for “Python DeBugger.” It’s a built-in module that turns your terminal into an interactive debugging environment. Instead of just printing values, you can control execution.

Explanation: You can set a “breakpoint” where you want execution to pause. Once paused, you can issue commands to inspect variables, step through code line by line, and see exactly what’s happening.

How to Start:

  1. The Old Way: Add import pdb; pdb.set_trace() at the point in your code where you want to start debugging.
  2. The Modern Way (Python 3.7+): Simply add breakpoint() at the desired location. This is cleaner and recommended.
    Concrete Example:

Let’s debug a recursive factorial function using breakpoint().

Python

def factorial(n):
    if n == 0:
        return 1
    else:
        result = n * factorial(n-1)
        return result

breakpoint() # Execution will stop here
print(factorial(5))

 

When you run this script, execution will stop at the breakpoint() line, and you’ll see a (Pdb) prompt in your terminal, ready for your commands.

### Step 5: Essential PDB Commands to Navigate Your Code

Once you’re at the (Pdb) prompt, you have a suite of commands at your fingertips. Here are the ones you’ll use 90% of the time.

  • l (list): Shows the current line of code and a few lines around it for context.
  • n (next): Executes the current line and moves to the next line in the current function. If the current line calls another function, it executes that entire function without stepping into its details.
  • s (step): Steps into the next function call. If the current line calls factorial(n-1), using s will take you inside that new factorial call.
  • c (continue): Continues execution normally until the next breakpoint (or the program ends).
  • p (print): Evaluates and prints the value of an expression. This is your main tool for inspection. Example: p n, p result, p n == 0.
  • pp (pretty print): Like p, but prints complex data structures (like nested lists or dictionaries) in a more readable format.
  • q (quit): Aborts the program entirely.
     

    💡 Pro Tip: You can just type the variable name directly at the (Pdb) prompt as a shortcut for p variable_name.

### Step 6: A Practical PDB Walkthrough

Let’s step through the factorial function from above.

  1. Run the script. Execution stops at breakpoint().

Plain Text

> /path/to/your/script.py(8)<module>()
-> print(factorial(5))
(Pdb)

Type s (step) to step into the factorial(5) call. You’ll now be at the first line of the function.

Plain Text

(Pdb) s
--Call--
> /path/to/your/script.py(1)factorial()
-> def factorial(n):
(Pdb) l

 

  1. Type l to see the context. It will show the function definition.
  2. Type n (next) to execute the definition and move to the first real line. You’re now at if n == 0:.

Plain Text

(Pdb) n
> /path/to/your/script.py(2)factorial()
-> if n == 0:
(Pdb) p n
5

 

  1. Type p n to print the value of n. It’s 5, as expected.
  2. Type n again. Since n is not 0, execution moves to the else block at the line result = n * factorial(n-1).
  3. Now, type s to step into the recursive call factorial(n-1). You’ll now be inside a new call to factorial with n=4.
  4. Repeat this process. You can watch the call stack grow, inspect n at each level, and see how the recursion unwinds. When a base case (n=0) is reached, you can use n to watch it return its value back up the chain.

     

    💡 Pro Tip: Getting lost? Use the w (where) command to print the current call stack, showing exactly which function calls led you to the current line.
    Ready to go deeper? Join our expert sessions

     

4. Common Mistakes

Even with the right tools, students often fall into the same traps. Here’s what to watch out for.

Mistake 1: Debugging Without a Hypothesis

  • What It Looks Like: Randomly adding print() statements all over the code, hoping one will reveal the bug. Changing code without really understanding the problem.
  • Why Students Make It: It feels like action, but it’s actually just guessing. It’s a reaction to frustration and panic.
  • How to Avoid It: Stop. Before adding a single print or breakpoint, ask yourself: “What do I expect to happen? What is a possible explanation for the wrong output?” Let this hypothesis guide where you place your debugging tools.
     

Mistake 2: Leaving Debugging Code in Your Final Submission

  • What It Looks Like: Submitting an assignment with print(“Value of x is:”, x) scattered throughout, cluttering the output. Or worse, forgetting a breakpoint() which causes the program to pause during grading.
  • Why Students Make It: Haste and lack of a final review process. You’re so relieved it works, you just submit.
  • How to Avoid It: Make it a habit to do a final “cleanup” pass. Review your code for any leftover print() or breakpoint() statements and remove them before final submission.

Mistake 3: Using Print Statements Inside Loops Without a Plan

  • What It Looks Like: A loop that runs 1000 times generates 1000 lines of output. You have to scroll endlessly to find the few lines that matter, or your terminal buffer overflows and loses the early output.
  • Why Students Make It: Not thinking about the scale of the output.
  • How to Avoid It: Print conditionally. print(f”Debug: i={i}”) if i % 100 == 0 else None. Or, better yet, in such scenarios, use pdb and set a conditional breakpoint (e.g., breakpoint() inside an if i == 500: block).
     

Mistake 4: Ignoring the Error Message

  • What It Looks Like: Seeing a TypeError or IndexError and immediately adding print statements, instead of reading the error message carefully.
  • Why Students Make It: Error messages can look scary or technical, so there’s a tendency to ignore them and jump straight to the code.
  • How to Avoid It: Train yourself to read error messages first. Python’s traceback tells you the type of error, the line number where it happened, and often a helpful hint. This is your most valuable piece of information. Start your investigation there.

5. FAQ Section

Here are answers to questions we hear all the time from students learning to debug.

1. Is it “cheating” to use print statements for debugging?
Absolutely not. Using print() is a fundamental and perfectly valid print statements technique. Every programmer, from beginners to experts, uses it. The key is to use it strategically, not as a crutch to avoid learning other methods.

2. What’s the main advantage of pdb over print()?
pdb is interactive. With print(), you have to predict what you need to see, add code, and re-run the program. With pdb, you can pause the program and explore its state live, without needing to predict everything in advance. It’s like exploring a cave with a flashlight versus just taking pictures at predetermined spots.

3. I added breakpoint() but nothing happens. The program just runs. Why?
Make sure you are running your script from the terminal/command line, not just an interactive shell. For example, use python my_script.py. The breakpoint() function is ignored in some interactive environments. Also, ensure you haven’t disabled breakpoints by setting the PYTHONBREAKPOINT environment variable.

4. Can I use pdb in Jupyter Notebooks?
Yes! While the standard breakpoint() might behave differently, Jupyter has built-in debugging tools. You can use the %debug magic command after an error occurs to enter a post-mortem debugger, or use the newer debugger command in the notebook interface itself for an interactive experience.

5. How do I handle debugging when my code involves user input?
Using pdb is perfect for this. Set your breakpoint after the input is collected. For example:

 

Python

user_data = input("Enter a number: ")
breakpoint() # Pause here after input, inspect user_data
number = int(user_data)
# ... rest of code

Now you can inspect user_data before any conversion happens.

6. My program is huge. Do I have to step through every single line with pdb?
No! That would be tedious. You set breakpoints at the specific functions or lines you suspect are causing the problem. Use c (continue) to run normally until your breakpoint is hit, then use n and s only in the problematic section.

7. What’s the difference between next and step in pdb?
next executes the current line and stops at the next line within the same function. If the current line contains a function call (my_function()), next runs that entire function and stops right after it. step will go inside my_function(), allowing you to debug its internal code line by line.

8. Can I change a variable’s value while debugging with pdb?
Yes! You can use the ! command followed by a Python assignment. For instance, if you’re stuck in a loop and want to force an exit condition to test something, you could type !n = 0 to set the loop variable n to zero. This is an advanced but incredibly powerful technique.

6. Conclusion

Debugging doesn’t have to be a dreaded chore. By shifting your mindset from frustrated guessing to systematic investigation, you transform errors into learning opportunities. You now have a powerful toolkit at your disposal.

Start with strategic print statements to quickly trace values and logic flow. When the situation gets more complex—with deep loops, recursion, or tricky interactions—don’t hesitate to deploy pdb python. Its interactive power lets you become a true detective, stepping through your code, inspecting its soul, and uncovering the root cause of any bug.

Remember, every error you fix makes you a stronger programmer. The key is to be patient, be curious, and use the right tool for the job. So next time your code doesn’t run as expected, take a deep breath, pick up your new debugging toolkit, and get to work. You’ve got this.

Ready to submit your assignment with confidence?

 

Read more articles on our blog to level up your programming skills.


Related Posts

Binary Search Explained: Algorithm, Examples, & Edge Cases

Master the binary search algorithm with clear, step-by-step examples. Learn how to implement efficient searches in sorted arrays, avoid common …

Mar 11, 2026
How to Approach Hard LeetCode Problems | A Strategic Framework

Master the mental framework and strategies to confidently break down and solve even the most challenging LeetCode problems.

Mar 06, 2026
Two Pointer Technique | Master Array Problems in 8 Steps

Master the two-pointer technique to solve complex array and string problems efficiently. This guide breaks down patterns, provides step-by-step examples, …

Mar 11, 2026

Need Coding Help?

Get expert assistance with your programming assignments and projects.