New: Try Voli The Bear, Fast package manager (and not only) for Windows
Updated Jun 19, 2026 Edit on GitHub

Why a Debugger Beats print()

Picture your usual debugging loop. Something's wrong, so you sprinkle in print() calls - print("here"), print(user), print("got past the check") - run the program, squint at the output, form a theory, and add more prints to test it. Each lap costs a code edit, a re-run, and patience. When the bug only shows on the 200th loop, or only when two unrelated values collide, that loop eats an afternoon. There's a way to skip it entirely - not the buttons yet, but why the whole approach is different.

What a debugger actually is

The mental model. A debugger pauses your program while it's running and hands you a frozen snapshot of that instant: every variable in scope, every caller that got you here. Ask "what is total right now?" and get the real answer, not a stale print from three runs ago.

print() debugging is taking a photo, walking away, developing the film, and only then seeing what you captured - point the camera wrong and you start over. A debugger is standing in the room with the lights frozen, free to look anywhere.

Why people get this wrong. Many treat the debugger as "the advanced thing" and reach for print() reflexively even when it's slower. It's the opposite: the debugger usually wins with less effort, because you stop editing-and-rerunning and start looking.

What it does in real life. Mark a line where the program should stop, then run it. Execution halts there and your editor shows the current state. Poke around, run one more line, poke around again - full visibility instead of guessing where to bolt on an observation point.

📝 Breakpoint. The line you mark as "stop here." The program runs normally until it hits that line, then pauses before running it, handing you control. More in Phase 2.

Where print() quietly fails you

The cost of print() is small frictions that add up:

  • You must predict what to look at. Every print() is a bet placed before you knew the answer; guess wrong and re-run to place a new one.
  • The edit-rerun tax. Each new question means changing the source and restarting - brutal for a slow-booting server or a minute-long test.
  • It only sees what you named. print(user) shows user, not the variable two frames up you didn't think to print.
  • The cleanup. You'll eventually commit a stray print("HERE!!!") to a pull request.

A debugger removes the bet: when paused, everything in scope is visible at once, and asking a new question is a glance, not a re-run.

When this saves you hours

The debugger pulls ahead in exactly the situations that make print() miserable:

  • The bug is deep in a loop or recursion. "Wrong on some iteration" means scrolling 200 lines of print() output. A breakpoint that only fires on the bad iteration drops you into the moment it goes wrong. (How, in Phase 3.)
  • You don't know where the bug is. Pause early and walk forward, watching values change until one goes wrong - no guessing which line to print near.
  • The state is large or nested. A request object, a nested config, a thirty-field ORM model - printing that is noise. A debugger expands it like a folder; read only what you need.
  • Re-running is expensive. Slow startup, a hard-to-reproduce setup, a ten-click flow to the bug - pause once, ask every question from that stop.

When print() is still fine

A guide that says always use the debugger would be lying. Print and log debugging is respectable, and sometimes the better tool:

  • You already have a strong hunch. 90% sure value is None on line 40? A single print(value) confirms it faster than launching a debug session.
  • The bug spans many runs or lives in production. A debugger pauses one execution. To spot a pattern across thousands of requests, or a failure you can't reproduce locally, structured logging wins - logs persist and aggregate; you can't attach a breakpoint to a server you can't reach.
  • Timing-sensitive and concurrent code. Pausing changes when things happen and can make a race condition vanish. Logging observes without stopping the clock. (More in Phase 3.)
  • The debugger isn't practical here. Minified code with no source maps, a tangled build, an unreachable environment - a log line is sometimes the path of least resistance, and that's okay.

💡 Key point. The skill isn't "always use the debugger" - it's knowing which tool the situation calls for. Most developers avoid it only because nobody walked them through it. After the next two phases, you'll pick on the merits.

Recap

  1. A debugger pauses your running program, showing everything true at that instant - no guessing, no re-running.
  2. print() forces you to predict what to observe and pay an edit-rerun tax per question; a breakpoint shows all state at once.
  3. The debugger wins big on deep loops, unknown bug locations, large nested state, and expensive re-runs.
  4. print() / logging still wins for strong hunches, patterns across many runs, production, and timing-sensitive code.

Now that you know why, let's learn the handful of controls that work in every debugger.


← Guide overview · Phase 2: The Core Moves →

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. What does a debugger let you do that print() does not?

2. When is print()/logging honestly still the better tool?

3. What is a breakpoint?