This page is intentionally minimal. I'm here to present an idea, not here to shove javascript down your throat.

We Still Debug Like It's the 1980s

What if debugging was querying execution instead of stepping through it?


Most modern debuggers still revolve around a single primitive: stepping. Step into. Step over. Step out. Advance one line at a time and watch the program unfold.

This interaction model feels natural largely because it is familiar. But it exists for historical reasons, not because it matches how humans reason about bugs.

Stepping made sense when computation was expensive

Early debuggers were designed under severe constraints: slow terminals, limited memory, and linear execution models. Inspecting state was costly, and programs were small enough that stepping through them line by line was often feasible.

In that environment, a debugger that advanced execution incrementally and showed you “what happens next” was a reasonable abstraction.

Those constraints no longer apply.

Most bugs are found by recognizing state, not time

In practice, bugs are rarely found by observing the passage of time. They are found when execution violates an expectation - when the program enters a state that should not exist.

The moment of interest is not “the next line”, but the first point where an invariant breaks:

These are predicates over program state, not moments in time. Yet most debuggers force us to advance linearly through execution, hoping to notice the violation as it happens.

When debugging, the question is rarely “what happens next?” It is usually something closer to “when does this happen?”

Stepping forces the programmer to translate that question into a sequence of manual actions: step, inspect, step, inspect, repeat. The debugger exposes time, even when time is not the thing we care about.

Stepping is a leaky abstraction

This creates friction because stepping exposes an implementation detail of execution rather than the intent of the investigation.

Consider the difference between these two mental models:

The second model is declarative. It describes a property of program state, independent of how many lines or frames it takes to get there.

What if debugging was querying execution?

An alternative approach is to treat debugging as querying execution rather than manually driving it.

Instead of repeatedly stepping and checking, you describe the state you are interested in, and let the debugger advance execution until that description matches reality.

For example:

Expressed as a predicate in Python style over execution state, that might look like:


(_event_ == "return")
and (_return_ is None)
and ("myhelper" in _callstack_)

This is not a new idea - conditional breakpoints have existed for decades - but treating this as the primary interaction model changes how debugging feels.

No debugger-specific language required

One important detail is that these conditions do not require a separate syntax or DSL. They can be expressed directly for some languages (here, Python), using ordinary expressions and values that represent execution state.

That makes the debugger feel less like a separate tool and more like an extension of the language runtime itself.

A small experiment

I explored this idea by building a small Python debugger where execution can be advanced by evaluating arbitrary Python expressions over the current execution state.

The tool itself is not especially important. What mattered was the shift in how debugging felt: less like driving a machine step by step, and more like asking questions and waiting for them to become true.

If we were starting today

If debuggers were designed today, without historical constraints, it's not obvious that stepping would be the central abstraction.

We might instead start from describing program state, and treat time as an implementation detail rather than the primary interface.

Stepping would still exist-but as one tool among many, not the foundation of the experience.


If you're curious, the prototype that motivated this essay/rant lives here: GitHub