Stop Clicking Resume: Conditional Breakpoints and Evaluate-and-Log in PyCharm
PyCharm breakpoints can do far more than stop on a line. Conditional breakpoints and Evaluate-and-Log turn a 10,000-click Resume marathon into a targeted stop or a silent trace.
14 Oct 2025, 15:59 UTC

You have a CSV-cleaning script that processes 10,000 rows and produces corrupt output exactly once. You put a breakpoint on the line that appends each cleaned row, start the debugger, and it stops on row one. Resume. Row two. Resume. Row three. Somewhere around click four hundred you start questioning your career choices.
The useful takeaway: PyCharm breakpoints are not just stop signs. They accept conditions, hit counts, and a non-suspending "Evaluate and log" mode, so the debugger can find the one bad iteration for you instead of the other way around.
Conditional breakpoints: stop only when it matters
Right-click any breakpoint (or open the breakpoints dialog from the Run menu) and you get a Condition field. It takes an ordinary Python expression, evaluated in the frame where the breakpoint sits. The debugger only suspends when the expression is true.
Say your cleaner looks like this:
for row in read_rows("input.csv"):
amount = parse_amount(row.get("amount"))
cleaned.append({"id": row["id"], "amount": amount})Put a breakpoint on the cleaned.append(...) line with the condition:
row.get("amount") is NoneNow the run sails through every healthy row and suspends only on a malformed one, with all local variables live for inspection. You can poke at row in the debugger console and see exactly what the parser choked on.
A sibling option worth knowing is hit count: stop on the Nth time the line is reached. That answers "the failure happens around iteration 8,000" without writing counter code. There is also a suspend policy (this thread vs. all threads, relevant in concurrent code), dependent breakpoints that only arm after another breakpoint fires, and a global mute switch that disables all breakpoints without deleting them.
Evaluate-and-Log: watch it without stopping
Sometimes you don't want to stop at all. You want to know which rows are bad and how many, across the whole file. That's what the Evaluate and log option is for: each time the breakpoint is hit, PyCharm evaluates your expression and prints the result to the debug console, and execution continues.
Take the same breakpoint, tick "Evaluate and log", and use an expression like:
f"bad row id={row['id']} amount={row.get('amount')!r}"Combine it with the same condition (row.get('amount') is None) so only interesting hits get logged. Run the script once to completion and the console holds a full list of every offending row — no clicking, no code changes. It's a poor man's tracepoint, and for one-off investigations it beats sprinkling print() calls you have to remember to remove.
The trade-off: your expressions run inside your process
Neither feature is free. The debugger evaluates the condition (and the log expression) on every hit, inside your running interpreter. In a tight loop that's real overhead — a breakpoint on a line executed a million times means a million extra expression evaluations. Keep conditions cheap.
Two sharper edges:
- Side effects. A condition like
cache.pop(key) == targetmutates your program every iteration. Conditions should be pure reads. - Exceptions. If your condition raises (say,
row['amount'] > 100on a row where the key is missing), behavior depends on the debugger version — typically the error is reported rather than silently ignored, but don't rely on a specific behavior. Write exception-safe expressions, e.g.row.get('amount') is not None and row['amount'] > 100.
If you suspect the overhead itself is distorting timing-sensitive code, measure it: run the loop once with the breakpoint active and once with breakpoints muted, and compare wall-clock times with time.perf_counter(). Don't trust anyone's benchmark numbers, including ones you might read in a blog post — measure on your machine, with your loop.
Choosing the right instrument
The decision is simpler than the feature list suggests:
- Conditional breakpoint — you need to stop on one anomalous case and inspect live state.
- Evaluate-and-Log — you need frequency or which-item answers across a full run.
- Real logging or metrics — the code runs somewhere an IDE can't attach (CI, production, a cron box). The debugger is a dev-time instrument, not an observability stack.
Two caveats before you rely on any of this. First, dialog layout and option names shift across PyCharm versions and keymaps; open Run > View Breakpoints in your installed build and confirm the Condition, Evaluate-and-log, and hit-count fields are where this post says they are. Second, edition packaging changed when JetBrains unified PyCharm's distribution in 2025: core local debugging features like these have historically been in the free tier, while remote interpreters (Docker, SSH, WSL) have been paid-tier territory — check the current feature matrix for your version before promising either to a team.
The fifteen-minute verification: write a loop to 10,000 with one planted bad value, set the conditional breakpoint above, and confirm it suspends exactly once. Then flip on Evaluate-and-Log, remove the condition's stop behavior, and confirm you get a one-line log and a completed run. After that, the Resume button can go back to being something you press on purpose.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.