Part 2: Breakpoints and Stepping Through Code

Introduction

In Part 1, you learned basic breakpointsβ€”click the gutter, execution stops. But VS Code offers much more: conditional breakpoints that pause only when specific conditions are met, logpoints that output messages without stopping, and step controls that let you navigate code execution precisely.

When debugging a data processing pipeline that handled 10,000 records, I needed to pause only when a specific user ID appeared. A regular breakpoint would have stopped 10,000 times. A conditional breakpoint? Once. This article teaches you the breakpoint types and stepping techniques that make debugging efficient.

Breakpoint Types

Regular Breakpoints - What You Know

def process_payment(amount, user_id):
    balance = get_balance(user_id)  # ← Red dot = always pauses here
    if balance >= amount:
        deduct(user_id, amount)
        return True
    return False

Pauses every time the line executes.

Conditional Breakpoints - Game Changer

Only pause when a condition is true.

Expression Condition

Setting it up:

  1. Right-click in gutter (or existing breakpoint)

  2. Choose "Add Conditional Breakpoint" β†’ "Expression"

  3. Enter: customer['tier'] == 'VIP'

  4. Breakpoint shows as red dot with condition icon

Now it only pauses for VIP customers!

Hit Count Condition

Pause after the line has been hit a certain number of times.

Use cases:

  • = 50 - Pause on exactly the 50th iteration

  • > 100 - Pause after 100 hits

  • % 10 == 0 - Pause every 10th hit

Real scenario: I was debugging a loop that processed 1000 database records. The bug appeared around record 500. Instead of clicking "Continue" 500 times, I set hit count = 500 and execution paused right where the bug occurred.

Triggered Breakpoints

Pause when another breakpoint is hit first.

Setup:

  1. Set regular breakpoint at line 2 (Breakpoint A)

  2. Right-click line 6 β†’ "Add Triggered Breakpoint"

  3. Select which breakpoint triggers it: "Breakpoint A"

Now Breakpoint B only activates after Breakpoint A is hit at least once.

Real scenario: Debugging authentication flow. I only wanted to debug process_data() when authentication succeeded (when it hit the auth breakpoint). Triggered breakpoints let Breakpoint B activate only after successful auth.

Logpoints - Debug Without Stopping

Output messages to Debug Console without pausing execution.

Setting it up:

  1. Right-click in gutter

  2. Choose "Add Logpoint"

  3. Enter: Updating {product_id} by {quantity}

Output in Debug Console:

Why use logpoints?

  • No code changes needed (no print statements!)

  • See output without stopping execution

  • Perfect for loops and high-frequency functions

  • No cleanup needed after debugging

Real example: I was debugging a webhook handler that received events continuously. Regular breakpoints would pause the server for every event. Logpoints let me see what was happening without disrupting service:

Debug Console showed every event without stopping the server.

Inline Breakpoints

Pause at specific expressions within a lineβ€”useful for complex one-liners or comprehensions.

Set inline breakpoint:

  1. During debug session

  2. Shift+F9 or right-click β†’ "Add Inline Breakpoint"

  3. Choose the exact expression

Real scenario:

Without inline breakpoints, I'd need to expand this into a loop to debug it.

Function Breakpoints

Pause when a function is called, even if you don't have the source code.

Setting it up:

  1. Open Breakpoints panel

  2. Click "+" β†’ "Function Breakpoint"

  3. Enter function name: calculate_tax

Real scenario: I was debugging an issue in third-party library code. I couldn't easily find the source file, but I knew the function name parse_jwt. Function breakpoint paused execution right when it was called.

Data Breakpoints (Python Limited Support)

Pause when a variable's value changes. Note: Python's debugger has limited support compared to languages like C++.

Stepping Through Code

Once paused at a breakpoint, step controls let you navigate execution precisely.

Step Over (F10)

Execute current line, don't enter function calls.

Use when: You trust the function being called and want to see the result without entering it.

Step Into (F11)

Enter the function on the current line.

Use when: You need to see what's happening inside a function.

Step Out (Shift+F11)

Exit the current function and return to the caller.

Use when: You stepped into a function but realized you don't need to debug it further.

Continue (F5)

Resume execution until next breakpoint or program ends.

Use when: You've seen what you needed and want to reach the next breakpoint.

Restart (Shift+Cmd+F5)

Stop current debug session and restart from the beginning.

Use when: You modified code or need to debug from the start again.

Stop (Shift+F5)

End the debug session.

Real-World Example: Debugging an API Data Transformation

I built an API endpoint that transformed database records into API responses. The output was incorrect for certain records.

The Code

Problem: API crashes for user_id 3 (Charlie) with AttributeError.

Debugging Strategy

Step 1: Conditional Breakpoint

Don't want to pause for all users, just user_id 3.

Breakpoint location: Line 7 (return {) Condition: user_data["user_id"] == 3

Step 2: Start Debugging

Execution pauses when processing user_id 3.

Step 3: Inspect Variables

Step 4: Test Fix in Debug Console

Step 5: Update Code

Total debugging time: 2 minutes using conditional breakpoint + debug console.

Without debugger: Would have added prints, tested each user, removed prints, restarted server multiple times. 15-20 minutes easily.

Advanced Stepping Techniques

Smart Stepping

VS Code can skip stepping into certain code like libraries or getters/setters.

Configure in settings:

Run to Cursor

Right-click any line β†’ "Run to Cursor" - Execution runs until it hits that line (like a temporary breakpoint).

Press F5, execution runs straight to step4() without stepping through everything above.

Jump to Cursor (Set Next Statement)

Caution: Advanced feature. Right-click β†’ "Jump to Cursor" moves execution pointer without running code between.

Use carefully: Only when you need to skip code or re-run code.

Debugging Loops Efficiently

Problem: Inspecting Loop Iterations

Strategies:

  1. Conditional breakpoint: item.id == 'problem-id'

  2. Hit count breakpoint: i == 50 (pause on 50th item)

  3. Logpoint: Item {i}: {item.id} - {item.status}

Real Example: Finding Bad Data

Conditional breakpoint condition: not row.get('email')

Only pauses on rows missing email addresses, skipping thousands of valid rows.

Exception Breakpoints

Pause when exceptions are raised.

Enable in VS Code

  1. Open Run and Debug view

  2. Breakpoints panel β†’ Check "Raised Exceptions"

Now execution pauses the moment an exception occurs, even if it's caught!

Incredibly useful for seeing exception state before it's caught.

Uncaught Exceptions Only

Check "Uncaught Exceptions" instead to only pause on exceptions that aren't caught.

Keyboard Shortcuts Mastery

Action
Shortcut
When to Use

Toggle Breakpoint

F9

Add/remove breakpoint on current line

Conditional Breakpoint

Right-click gutter

Only pause when condition met

Continue

F5

Resume to next breakpoint

Step Over

F10

Execute line, don't enter functions

Step Into

F11

Enter function calls

Step Out

⇧F11

Exit current function

Restart

β‡§βŒ˜F5

Restart debug session

Stop

⇧F5

End debugging

Run to Cursor

Right-click + select

Quick temporary breakpoint

Best Practices

1. Use Conditional Breakpoints for Loops

Instead of:

Use:

2. Logpoints for High-Frequency Code

Instead of:

Use:

3. Step Over Trusted Functions

Don't waste time stepping through functions you know work correctly.

4. Use Run to Cursor for Quick Navigation

When you know exactly where you want to pause, right-click β†’ "Run to Cursor" is faster than setting a temporary breakpoint.

What's Next

You now know:

  • Conditional, logpoint, triggered, inline, and function breakpoints

  • Stepping controls: over, into, out, continue

  • Debugging loops efficiently

  • Exception breakpoints

In Part 3: Variables, Watch, and Data Inspection, you'll master inspecting complex data structures, using the watch panel, exploring call stacks, and leveraging the debug console REPL for advanced debugging.


Based on debugging thousands of functions in production Python applications, from simple scripts to complex async microservices.

Last updated