skip to content

questions

4

What does Python's built-in breakpoint() do, and how does PYTHONBREAKPOINT control it?

level: juniorimportance: must knowfreq 45%

answer

  1. A builtin that pauses the program
  2. The pause routes through a hook
  3. sys.breakpointhook chooses the debugger
  4. One environment variable disables every call
  5. Defaults to pdb.set_trace since 3.7

basics

~20 s

Calling breakpoint() pauses the program and drops into a debugger. It dispatches through sys.breakpointhook, which by default calls pdb.set_trace. Setting PYTHONBREAKPOINT=0 makes every call a no-op; setting it to a dotted callable path routes calls there instead.

solid answer

~40 s

`breakpoint()` is a builtin added in Python 3.7 (PEP 553). It does not hard-code a debugger: it forwards all its arguments to whatever `sys.breakpointhook` currently points at, and the default hook calls `pdb.set_trace()`, so execution stops on the next line with a `(Pdb)` prompt. The indirection is the point. The default hook consults the `PYTHONBREAKPOINT` environment variable when it runs, so `PYTHONBREAKPOINT=0` turns every `breakpoint()` call in the process into a no-op without editing code, and `PYTHONBREAKPOINT=some.module.func` sends every call to that callable instead — which is how third-party debuggers hook themselves in. You can also assign `sys.breakpointhook` yourself at runtime and restore it from `sys.__breakpointhook__`. Before 3.7 the equivalent was the two-statement `import pdb; pdb.set_trace()`.

code

python · 8 lines
python
import sys

def log_hook(*args, **kwargs):
    print("paused at breakpoint", args, kwargs)

sys.breakpointhook = log_hook
breakpoint("route-leg", row=6789)
sys.breakpointhook = sys.__breakpointhook__

go deeper

for a junior

Recall that breakpoint() is a builtin needing no import, that it stops the program at an interactive debugger prompt, and that c continues while q quits. Knowing PYTHONBREAKPOINT=0 disables it is the bonus that shows you have used it outside a tutorial.

for a middle

Explain the indirection: the builtin forwards its arguments to sys.breakpointhook, whose default calls pdb.set_trace, and the environment variable is read by that default hook. Be ready to rebind the hook in code and restore it from sys.breakpointhook.

for a senior

Show the operational judgement: disable breakpoints in deployed environments, know that a stray call in a tty-less worker hangs or dies, remember that only the calling thread stops, and enforce a lint rule so debug calls never reach a release branch.

for a principal

Own the policy angle. Decide whether the platform sets PYTHONBREAKPOINT=0 everywhere by default, how developers opt back in locally, whether a shared hook that logs and continues is safer than one that pauses, and how that interacts with the team's debugging and incident practice.

## The builtin and the hook are two different things `breakpoint()` is a plain builtin function, available since **Python 3.7** (PEP 553). Its entire body is: look up `sys.breakpointhook`, call it with whatever positional and keyword arguments `breakpoint()` received, and return the result. It contains no debugger logic at all. The default value of `sys.breakpointhook` calls `pdb.set_trace()`, which is why an unconfigured interpreter stops at a `(Pdb)` prompt. The original, still-valid spelling is `import pdb; pdb.set_trace()` — one import plus one call, on one line, which is exactly the awkwardness PEP 553 removed. The builtin needs no import, is a single short word that is easy to grep for before committing, and — crucially — is *interchangeable*. ## PYTHONBREAKPOINT The default hook reads the `PYTHONBREAKPOINT` environment variable when it is invoked, not once at interpreter start, so changing the process environment at runtime affects later calls. * **Unset** — the debugger starts, as `pdb.set_trace()`. * **`PYTHONBREAKPOINT=0`** — the hook returns immediately. Every `breakpoint()` call in the process becomes a no-op. This is the safety valve: a `breakpoint()` accidentally shipped to a batch job cannot hang it if the environment disables breakpoints globally. * **`PYTHONBREAKPOINT=pkg.mod.func`** — the hook imports `pkg.mod`, looks up `func` and calls it with the forwarded arguments. Any debugger that exposes a suitable entry point can be wired in this way without a source change. If the name cannot be imported, CPython emits a `RuntimeWarning` and the call simply returns rather than crashing your program. ## Setting the hook in code `sys.breakpointhook` is an ordinary module attribute you can rebind. A test harness might point it at a function that records the call and continues; a service might point it at one that logs and raises. `sys.__breakpointhook__` holds the original value so you can restore it, exactly as `sys.__stdout__` mirrors `sys.stdout`. Because the arguments are forwarded, `breakpoint(row, tag="leg")` is meaningful when the hook expects them — a custom hook can accept context. The default `pdb`-based hook accepts a `header` keyword only, so passing arbitrary positional arguments to the *default* hook is an error. ## Where it stops, and what to type After the debugger starts, execution is suspended at the *next* statement to be executed in that frame and you get an interactive prompt: `c` continues, `n` runs the next line, `q` quits the debugger (which raises out of the program via `bdb.BdbQuit`), `p expr` prints an expression evaluated in the paused frame. Everything you type is evaluated in the paused frame's own namespace, so locals are readable and assignable. ## Production hazards worth naming in an interview 1. **It needs a terminal.** `pdb` reads from standard input. In a container, a systemd unit or a worker with no attached tty, a stray `breakpoint()` either hangs the worker forever or dies on a closed stdin. `PYTHONBREAKPOINT=0` in the deployment environment is cheap insurance. 2. **It stops one thread.** `pdb.set_trace()` suspends the thread that executed it. Other threads keep running, keep holding locks and keep taking work, so a paused process is not a frozen snapshot of the whole program. 3. **It is not a logging tool.** A `breakpoint()` left in code is a landmine; a linter rule that fails the build on the builtin is standard practice, and grepping for one short word is why the builtin is easier to police than the two-statement `pdb` form. 4. **It ignores `-O`.** Unlike `assert`, `breakpoint()` is a normal function call and optimisation flags do not remove it — only the environment variable neutralises it. ## The interview shape The question is rarely "what is a breakpoint". It is "why is there a hook in the middle", and the answer is: so that *which* debugger runs, and *whether* one runs at all, is an operational decision made outside the source file — by an environment variable in a deployment, or by an editor that installs its own hook — instead of a code edit that must be reverted before commit.

  • What happens if PYTHONBREAKPOINT names a callable that cannot be imported?
    The default hook fails soft. CPython emits a RuntimeWarning saying the name is unimportable and the breakpoint() call returns immediately, so the program continues rather than crashing. That is deliberate: a typo in a deployment environment variable should not take down a job. You only learn about it from the warning, so treat warnings as visible output in any environment where you set the variable.
  • Why is breakpoint() considered safer to ship accidentally than a bare pdb.set_trace() call?
    Neither is safe, but breakpoint() is centrally neutralisable: PYTHONBREAKPOINT=0 in the deployment environment turns every call in the process into a no-op, while a direct pdb.set_trace() call ignores that variable and will still try to open a prompt. It is also one short, unambiguous token, so a lint rule or a pre-commit grep catches it reliably.
  • Does breakpoint() stop the whole process when other threads are running?
    No. The default hook suspends only the thread that called it. Other threads keep executing, keep acquiring locks and keep consuming work items, so what you inspect at the prompt is one frame stack in a program that is still moving underneath you. For anything concurrent, treat the paused frame as a sample, not a snapshot.

It is a light switch wired to a socket rather than to a bulb: the code says "stop here", and the environment decides which debugger is plugged in — or that nothing is.

saying these in an interview costs you the question

  • Claims breakpoint() is just an alias for pdb.set_trace with no hook
  • Thinks PYTHONBREAKPOINT only picks a debugger and cannot disable breakpoints
  • Believes running with -O strips breakpoint() calls like assert
  • Assumes breakpoint() freezes every thread in the process
  • Says breakpoint() needs an import to work
  • Thinks a bad PYTHONBREAKPOINT value crashes the interpreter

context

open as a page

In pdb, how do the step, next, until and return commands differ when stepping?

level: middleimportance: must knowfreq 50%

basics

~10 s

In pdb, step enters a called function, next runs the call and stays in the current frame, return finishes the current function, and until runs forward past the current line number.

open as a page

How do you use pdb's conditional breakpoints to catch a duplicated side effect in a 6,800-row batch?

level: seniorimportance: should knowfreq 38%

basics

~10 s

Break on the code that performs the side effect, not on the loop, with a condition so it fires only for the offending row. Then use where and up to compare the caller frames.

open as a page

In pdb, how do you print a variable whose name collides with a command such as n or c?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

The pdb prompt matches its own commands first, so typing n runs next. Use p n to print the value, pp n to pretty-print it, or an exclamation-mark prefix to run the line as Python.

open as a page