Python • Debugging and Testing

pdb & IDE Debugging — Use pdb or IDE debugging concepts (breakpoint/step/inspect) (intro)

Learn to pause, inspect, and step through your Python code to find and fix bugs systematically.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

Historical Context & Motivation

If you have ever added a dozen print() statements throughout your code just to figure out why a variable has the wrong value, you already understand the problem that debuggers were invented to solve. Early programmers literally had to trace through machine instructions by hand, sometimes reading punched cards one at a time. As programs grew larger and more complex, the need for automated tools to pause execution and examine a program's internal state became critical.

The word "bug" in computing dates back to the 1940s, when a moth was found stuck in a relay of the Harvard Mark II computer. Since then, the concept of debugging — the systematic process of finding and removing errors — has evolved from hand-tracing to sophisticated interactive tools. Python's built-in debugger, pdb, and modern IDE debuggers (like those in VS Code or PyCharm) give you the power to freeze your program mid-run and look under the hood.

1947 • The First "Bug"

Grace Hopper's team discovers a moth in the Mark II relay, popularizing the term "debugging" for removing program errors.

1970s • Early Interactive Debuggers

Command-line debuggers like GDB appear for C programs, introducing breakpoints, stepping, and variable inspection to the developer workflow.

1991 • Python & pdb Are Born

Guido van Rossum releases Python 1.0 with pdb included in the standard library, giving Python developers a built-in debugging tool from day one.

2010s • IDE Debuggers Go Mainstream

Visual Studio Code, PyCharm, and other IDEs introduce graphical debugging interfaces with click-to-set breakpoints, variable watch panels, and call stack viewers.

2018 • Python 3.7 Adds breakpoint()

The built-in breakpoint() function is introduced, making it even easier to drop into the debugger without importing pdb manually.

So the central question is: how can you move beyond scattering print() statements and instead use a structured, interactive approach to find bugs? That is exactly what breakpoints, stepping, and inspecting are designed to do.

SECTION 2

Core Principles of Debugging

Debugging with pdb or an IDE revolves around three fundamental actions. These three concepts form the backbone of every debugging session, whether you are using a simple terminal-based tool or a full graphical IDE. Understanding them turns you from someone who guesses at bugs into someone who systematically hunts them down.

1

Breakpoint

A marker you place on a specific line of code that tells the debugger: "Pause execution right here." When the program reaches that line, it freezes so you can examine everything.
2

Step

Once paused at a breakpoint, you can execute your program one line at a time. Step Over runs the current line and moves to the next. Step Into dives inside a function call.
3

Inspect

While paused, you can check the current value of any variable, evaluate expressions, and see exactly what data your program is working with at that moment in time.
4

Continue

Resume normal execution after inspecting. The program runs at full speed until it hits the next breakpoint, the program ends, or an error occurs.
✦

KEY TAKEAWAY

Think of debugging like watching a movie with a remote control. A breakpoint is pressing pause at a specific scene. Stepping is advancing frame by frame. Inspecting is zooming in on any detail in the frozen frame. Without the remote, you can only watch the movie at full speed and try to catch errors as they fly by — that is what print() debugging feels like.

SECTION 3

Visual Explanation — The Debugging Workflow

The diagram below illustrates the typical debugging workflow. You start by writing code, then set a breakpoint at a suspicious line. When you run the program in debug mode, execution pauses at that line. From there you can inspect variables, step through lines one at a time, or continue to the next breakpoint. This cycle repeats until you find and fix the bug.

Debugging Workflow1. Write CodeCreate your program2. Set BreakpointMark line to pause at3. Run in DebugProgram starts executing⏸ PAUSEDAt breakpoint lineInspectCheck variable valuesStepExecute next lineContinueResume executionstep moves to next line,still pausedRuns to next breakpoint or end5. Fix the BugEdit code & re-run
The debugging cycle: set a breakpoint, pause, then use inspect, step, and continue to find the source of errors.

Notice that debugging is a cycle, not a straight line. After you step through a few lines and inspect variables, you might continue running to the next breakpoint, find more clues, and step again. The key insight is that at every pause, you have complete access to your program's state — every variable, every data structure, and even the order of function calls that brought you to that point.

SECTION 4

How pdb and IDE Debuggers Work

Python's pdb module is a text-based interactive debugger that comes built into Python's standard library. You do not need to install anything — it is always available. There are two main ways to activate it in your code.

Method 1: The breakpoint() Function (Python 3.7+)

Simply write breakpoint() on any line of your program. When Python reaches that line, it drops you into the pdb interactive prompt, where you can type commands to inspect and step through your code.

Method 2: import pdb; pdb.set_trace()

Before Python 3.7, the standard way was to write import pdb; pdb.set_trace() at the line where you wanted to pause. This does the same thing as breakpoint() but requires the explicit import. You will see both in online tutorials, and they are functionally equivalent.

Essential pdb Commands

CommandShortcutWhat It Does
nextnExecute the current line and move to the next one (Step Over).
stepsStep Into the current function call to see what happens inside it.
continuecResume normal execution until the next breakpoint is hit.
print(var)p varPrint the current value of a variable (Inspect).
listlShow the source code around the current line.
quitqExit the debugger and stop the program.

Core pdb commands you will use most often.

IDE Debugging: The Graphical Approach

If you use an IDE like VS Code or PyCharm, you get the same breakpoint/step/inspect capabilities but with a graphical interface. Instead of typing commands, you click in the margin next to a line number to set a breakpoint (usually shown as a red dot). You then press a "Run and Debug" button, and when execution pauses, a panel shows you all current variables, the call stack (the chain of function calls), and toolbar buttons for Step Over, Step Into, Step Out, and Continue. The concepts are identical to pdb — the interface is just more visual.

SECTION 5

Stepping Modes Explained

One of the trickiest parts of debugging is understanding the difference between the various stepping modes. When your program calls a function, should the debugger jump inside that function, or treat it as a single operation and move on? The diagram below clarifies the three stepping options.

Step Over vs Step Into vs Step Outmain.py1total = 02for i in range(3):3 total = add(total, i) ◄4print(total)▲ Debugger is paused on line 3def add(a, b):5 result = a + b6 return resultInside add() functionSTEP OVER (n)Runs add() entirely, thenpauses at line 4.You stay in main.py.STEP INTO (s)Jumps inside add() andpauses at line 5.You enter the function tosee what happens inside.STEP OUT (r)If you're already inside add(),this finishes it and pauses backat line 3 in main.py.Useful when you accidentallystepped into the wrong function.
Comparison of the three stepping modes. Step Over treats a function call as one step. Step Into enters the function. Step Out leaves a function early and returns to the caller.
💡

Quick Tip

In pdb, the Step Out command is return (shortcut r). In IDE debuggers like VS Code, it is a button with an upward arrow icon labeled "Step Out." The idea is the same: finish running the current function and go back to wherever it was called from.

SECTION 6

Worked Example — Debugging a Buggy Function

Let us walk through a realistic debugging session. Consider this function that is supposed to compute the average of a list of numbers, but it returns the wrong answer.

Here is the buggy code:

def average(numbers): total = 0 for num in numbers: total += num return total / len(numbers) + 1 # Bug is here!

When we call average([10, 20, 30]), we expect 20.0 but get 21.0. Let us debug it step by step using pdb.

Debugging average() with pdb

  1. 1

    Step 1 — Insert a Breakpoint

    Add breakpoint() at the top of the function, right after def average(numbers):. This will pause execution as soon as the function is called.
    Code now reads: def average(numbers): breakpoint() total = 0 ...
  2. 2

    Step 2 — Run the Program

    Run your script with python my_script.py. The program starts and immediately pauses at the breakpoint. You see the pdb prompt: (Pdb).
    The terminal shows: > my_script.py(3)average() -> total = 0 (Pdb)
  3. 3

    Step 3 — Inspect the Input

    Type p numbers to print the value of the numbers parameter. This confirms the function received the correct input.
    Output: [10, 20, 30] — looks correct.
  4. 4

    Step 4 — Step Through the Loop

    Type n repeatedly to step over each line. After the loop finishes, type p total to check the accumulated sum.
    Output: 60 — that is correct (10 + 20 + 30 = 60).
  5. 5

    Step 5 — Inspect the Return Expression

    Before the return line executes, type p total / len(numbers) + 1 to evaluate the expression. Python's order of operations calculates 60 / 3 first (getting 20.0) and then adds 1, giving 21.0. The + 1 is the bug! It should not be there, or it needs parentheses.
    Bug found! The fix is to remove + 1 from the return statement. The correct line is return total / len(numbers).
⚠

Remember

After fixing the bug, always remove or comment out the breakpoint() call so your program runs normally in production. Leaving breakpoints in shipped code is a common beginner mistake!

SECTION 7

pdb vs IDE Debugging vs print()

Now that you understand both approaches, let us compare them directly. Each method has its place, and experienced developers use all three depending on the situation.

Featureprint() Debuggingpdb (Terminal)IDE Debugger
Setup requiredNone — just add print()None — built into PythonNeed an IDE installed
Pause execution?No — program runs to the endYes — pauses at breakpointsYes — pauses at breakpoints
Inspect variablesOnly what you printAny variable, any expressionAny variable, with a GUI panel
Step through codeNot possibleYes — n, s, r commandsYes — toolbar buttons
Learning curveVery easyModerate — need to learn commandsModerate — need to learn the UI
Best forQuick checks, simple scriptsRemote servers, any environmentComplex projects with many files

Comparison of three debugging approaches.

✦

KEY TAKEAWAY

Think of print() debugging like shining a flashlight in a dark room — you can only see where you point it. A proper debugger is like turning on all the lights at once. You see everything: every variable, every function call, every piece of data. Use print() for quick checks, but reach for pdb or your IDE debugger when the bug is tricky or you do not even know where to start looking.

SECTION 8

Connection to Advanced Debugging

The breakpoint/step/inspect cycle you learned here is the foundation, but professional debugging tools offer even more power. As you advance, you will encounter these more sophisticated features.

Introductory ConceptAdvanced Version
Simple breakpoints (pause at a line)Conditional breakpoints — pause only when a condition is true, e.g., only when x > 100
Inspect one variable at a timeWatch expressions — automatically track how a value changes over time
pdb in the terminalRemote debugging — connect your IDE to code running on a different machine or server
Step through line by linePost-mortem debugging — examine the program state at the exact moment an unhandled exception occurred
Manual breakpoint placementLogging frameworks — strategic, permanent instrumentation using Python's logging module

How introductory debugging concepts connect to advanced techniques.

You do not need to learn all of these right now. The important thing is that mastering breakpoints, stepping, and inspecting gives you a transferable skill. Every programming language — JavaScript, Java, C++, Go — has debuggers with these same three concepts. Once you internalize them in Python, you can debug anywhere.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words the difference between a breakpoint and a print() statement when debugging. Why might a breakpoint be more useful than a print statement for finding complex bugs?
Show answer
A print() statement shows you only the specific value you chose to print, and the program keeps running past it. A breakpoint pauses the entire program at that line, letting you inspect any variable, evaluate expressions, and step through subsequent lines one at a time. For complex bugs, you often don't know which variable to look at in advance. A breakpoint lets you explore freely, which is why it is more powerful for tricky issues.

Question 2: BASIC CALCULATION

You have this code and you insert breakpoint() on line 3: x = 5 y = x * 2 breakpoint() z = y + 3 print(z) When the debugger pauses, what values will p x and p y show? What about p z?
Show answer
When the breakpoint on line 3 triggers, lines 1 and 2 have already executed. So p x shows 5 and p y shows 10 (since 5 × 2 = 10). However, p z would produce a NameError because line 4 has not executed yet — z has not been defined. The breakpoint pauses before the line it sits on, but after all lines above it.

Question 3: INTERMEDIATE

You are debugging this code with pdb: def multiply(a, b): return a * b def process(values): breakpoint() result = 1 for v in values: result = multiply(result, v) return result print(process([2, 3, 4])) You want to see what happens inside multiply() during the first loop iteration. What pdb commands would you type, in order, to get there?
Show answer
Starting from the breakpoint at line 5, you would type: n to execute result = 1, then n to enter the for loop, then s (step into) when the debugger reaches result = multiply(result, v). Using s at the multiply call enters the function, pausing at return a * b. You can then use p a and p b to see the arguments (1 and 2 on the first iteration).

Question 4: APPLIED

A classmate wrote a function to count vowels in a string, but it always returns 0. The code is: def count_vowels(text): count = 0 vowels = 'aeiou' for char in text: if char in vowels: count + 1 return count Describe how you would use pdb to identify the bug. Which line would you place the breakpoint on, and which variable would you inspect after each iteration?
Show answer
Place breakpoint() inside the for loop, right before the if statement. Step through with n and inspect count after each iteration. You would notice that even when the if condition is True and the line count + 1 executes, the value of count stays at 0. The bug is that count + 1 computes a value but does not assign it back to count. The fix is count += 1 (or count = count + 1). The debugger makes this obvious because you can watch count remain unchanged.

Question 5: CRITICAL THINKING

Consider a web application that processes thousands of user requests per second. Explain why you cannot simply add breakpoint() to debug a live web server. What alternatives from the "Advanced Debugging" section (Section 8) would be more appropriate, and why?
Show answer
Adding breakpoint() to a live web server would freeze the entire server when it hits the breakpoint, blocking all user requests — this is unacceptable in production. Instead, you would use logging frameworks to record variable values and program flow without pausing execution. Conditional breakpoints could be used in a staging environment to pause only when a specific condition triggers the bug. Remote debugging could connect an IDE debugger to a test copy of the server. Post-mortem debugging could examine the state after an error occurs without affecting live traffic. The key insight is that interactive debugging requires pausing, which is only appropriate during development, not on live systems.

SUMMARY

Summary

Summary

Python provides powerful debugging tools that go far beyond print() statements. The built-in pdb module and modern IDE debuggers both revolve around three core actions: setting breakpoints to pause execution at a specific line, stepping through code one line at a time (using Step Over, Step Into, and Step Out), and inspecting variables and expressions while the program is paused.

You can activate pdb using breakpoint() (Python 3.7+) or import pdb; pdb.set_trace(). The essential commands are n (next / step over), s (step into), c (continue), p (print / inspect), and q (quit). These same concepts apply to every IDE debugger and transfer to every programming language, making this one of the most universally useful skills in computer science.

Varsity Tutors • Python • pdb & IDE Debugging — Use pdb or IDE debugging concepts (breakpoint/step/inspect) (intro)