Tools8 min read

Conditional Breakpoints: When and How to Use Them Beyond the Basics

Conditional breakpoints stop only when a specific condition is true, reducing noise in loops and high-frequency code. Here's how to wield them in Chrome DevTools, VS Code, and IntelliJ, plus a war story about a subtle race condition.

debuggingbreakpointsconditional breakpointsChrome DevToolsVS CodeIntelliJperformance

You know that moment: you're stepping through a loop that runs 10,000 times, pressing F9 like a zombie, waiting for iteration 4,723 where the bug finally appears. There's a better way. Conditional breakpoints stop execution only when a condition you specify is true. They're not new, but most engineers use them only for the simplest cases — like `i === 5`. This article goes deeper: performance trade-offs, real-world race conditions, and how to avoid common traps.

I'll focus on three environments: Chrome DevTools (for front-end), VS Code (general JavaScript/TypeScript), and IntelliJ IDEA (for Java/Kotlin). The principles apply elsewhere, but the specifics matter.

The Basics (Briefly)

A conditional breakpoint is just a breakpoint with an expression that must evaluate to `true` for the debugger to pause. In Chrome DevTools, right-click the line number, select 'Add conditional breakpoint', type your condition. In VS Code, right-click the red dot, select 'Edit Breakpoint...', type away. In IntelliJ, right-click the breakpoint, check 'Condition' and write a Java/C#/Kotlin expression.

Simple example: you want to stop when `user.id === 42` inside a list render. Write `user.id === 42`. Done.

info

Not all debuggers evaluate conditions the same way. Chrome DevTools uses JavaScript (even for TypeScript source maps). VS Code uses the language of the file. IntelliJ uses the runtime language. Know which language your condition is in.

The War Story: A Race Condition Masked by a Regular Breakpoint

Last year, I was debugging a React app where a modal flickered open and closed when a user clicked a button. The code looked straightforward:

Simplified version of the flicker bug
function handleClick() {
  setModalOpen(true);
  fetch('/api/data').then(() => {
    setModalOpen(false);
  });
}

I set a breakpoint on the first line. Every click paused. I stepped through — modal opened, fetch resolved, modal closed. No flicker. I tried a dozen clicks. Nothing. Then I added a conditional breakpoint: `this.state.modalOpen === true && this.state.modalOpen === false` (impossible condition). Wait, no. I wanted to stop when modal was both open and closed? That didn't make sense. Actually, I wanted to stop when the modal was already open and we tried to open it again. Condition: `this.state.modalOpen === true`. Stopped immediately. I saw that the fetch from a previous open was resolving after a second click, closing the modal that the second click just opened. Race condition. A regular breakpoint would have taken me forever to catch because the timing changed. Conditionals preserved the timing.

Conditional breakpoints preserve the exact timing of your app because they don't pause on every hit — only when the condition matches.

Performance: The Hidden Cost

Every time the breakpoint is hit (even if the condition is false), the debugger pauses execution briefly to evaluate the condition. In high-frequency code — animation frames, socket messages, or tight loops — this can add up. I've seen a conditional breakpoint in a `requestAnimationFrame` callback drop the frame rate from 60fps to 12fps. The condition was `someFlag === true`, which was false 99% of the time. Still, the evaluation cost killed performance.

Use logpoints (also called tracepoints) instead of conditional breakpoints when you only need to log a value. In Chrome DevTools, right-click a line, select 'Add logpoint', and type a message like `'user id:', user.id`. Logpoints don't pause execution — they just print to the console. In VS Code, you can use the 'Logpoint' option from the breakpoint context menu. In IntelliJ, it's called 'Breakpoint properties' > 'Log evaluated expression'.

12 fps

Frame rate drop caused by a conditional breakpoint in requestAnimationFrame

Side Effects in Conditions: Don't Do It

I've seen people write conditions like `counter++ === 5` to stop on the fifth hit. This works, but it mutates `counter`. If you later remove the breakpoint but forget that the counter was being incremented, your app's state is now different. Worse, if the breakpoint is in a hot path, the counter might overflow or cause unexpected behavior. Keep conditions pure: read-only expressions. Use a global counter variable that you increment manually if you must, but better yet, use hit counts if your debugger supports them.

warning

Never use assignment (=) in a condition. `if (x = 5)` is always true (and assigns 5). Use `===` for comparison. This is the most common typo in conditional breakpoints.

Hit Counts vs. Conditions

Chrome DevTools and IntelliJ both support hit counts: stop after the breakpoint has been hit a certain number of times. This is a special case of a conditional breakpoint, but it's evaluated more efficiently because the debugger doesn't have to run a full expression — just an integer increment. Use hit counts when you want to stop on, say, the 100th iteration of a loop. Conditions are for when the stopping point depends on a value, not an index.

Combining hit count and condition for efficiency
// Instead of: i >= 100 && items[i].status === 'error'
// Use a two-step: first hit count breakpoint at i === 100, then inside that scope set a conditional breakpoint.

Async Code and Conditions

You cannot use `await` or any async operation inside a breakpoint condition. The debugger evaluates the condition synchronously on the main thread. If you need to stop based on an async result, you must restructure your code to check the condition synchronously, or use a different approach like a logpoint that triggers an async action (e.g., sending a message).

For example, if you want to stop when a Promise resolves, you can't. Instead, put a regular breakpoint inside the `.then()` and use a condition on some variable that is set before the promise. Or use a blackboxing technique: ignore library code and set conditions on your own code.

Multi-Language Considerations

In Chrome DevTools, even if your source maps point to TypeScript, the condition is evaluated as JavaScript. You cannot use TypeScript type annotations or syntax. In VS Code, the condition is evaluated in the language of the file you're debugging (JavaScript, TypeScript, Python, etc.). In IntelliJ, it's the runtime language (Java, Kotlin, etc.).

Be aware that some languages have short-circuit evaluation that can mask errors. For example, in Java, `obj != null && obj.getStatus() == 5` is safe. In JavaScript, `obj && obj.status === 5` works. But if you write `obj.status === 5 && obj != null`, the order matters — you'll get a TypeError on the first part if obj is null.

lightbulb

Test your condition in the console first. In Chrome DevTools, you can copy the expression and run it in the console while paused at the same location. This validates the condition without the overhead.

Advanced: Conditional Logpoints in Production

Some debuggers (like Chrome DevTools) allow logpoints that work even in production builds if source maps are available. You can set a logpoint that logs a value only when a condition is true. This is incredibly powerful for debugging issues that only happen in production. For example, you can set a logpoint on an API call that logs the request payload only when the user ID is a specific value. No code changes needed.

But be cautious: logpoints can still have side effects if you evaluate expressions. In Chrome DevTools, logpoints evaluate the expression you type (e.g., `'user:', user.name`). If `user.name` has a getter that mutates state, you're in trouble. Keep logpoint expressions simple.

  1. 1Use a regular breakpoint when you need to pause every time.
  2. 2Use a conditional breakpoint when you need to pause only when a specific value or state is present.
  3. 3Use a hit count breakpoint when you need to pause after N iterations.
  4. 4Use a logpoint (with or without condition) when you only need to log, not pause.

Conditional breakpoints are a scalpel, not a sledgehammer. Used correctly, they save hours of manual stepping. Used carelessly, they introduce performance problems and side-effect bugs. Know your debugger's quirks, and always question whether you really need to pause or just log.

Frequently asked questions

Do conditional breakpoints slow down execution in Chrome DevTools?

Yes, because the debugger evaluates the condition at every hit before deciding to stop. In hot loops (e.g., rendering frames), this can drop frame rates significantly. Use logpoints instead if you only need to log.

Can I use a conditional breakpoint to modify a variable?

You can, but it's a terrible idea. Side effects in conditions make the debugger state diverge from production, and it's easy to forget to remove them. Always keep conditions side-effect free.

How do I set a conditional breakpoint in VS Code?

Right-click on the breakpoint glyph (red dot) and select 'Edit Breakpoint...' or use the keyboard shortcut Ctrl+Shift+F9. Then enter a JavaScript expression. The breakpoint stops only when the expression evaluates to true.

Can I set a conditional breakpoint that only triggers after a certain number of hits?

Yes, most debuggers support hit counts. In Chrome DevTools, right-click the breakpoint and select 'Edit breakpoint...' then choose 'Hit count' from the dropdown. In VS Code, you can combine a condition like `counter > 5`.