DEV Community

Fu liang
Fu liang

Posted on Originally published at regextester.dev

I built a regex debugger that shows every backtrack (flame graph included)

Testers tell you what a regex matches. A debugger tells you why — and where it burns time.

Every regex tester shows the same three things: matches, groups, and an explanation of the pattern. That covers the happy path. It stops helping the moment a regex is almost right — matching one tag too many, missing a case you can't reproduce, or taking 400 ms on an input that looks identical to the one that took 4.

The failure modes of a backtracking engine are invisible from the outside. A pattern doesn't slowly get slower — it tries a prefix, gives characters back, retries the inner quantifier at a different split, fails, and repeats that a few thousand times. The match result looks the same as the fast version's. Only the trace of what the engine actually did tells them apart.

So I built a full debugger into RegexTester.dev. Here's what it does.

One click from the tester

The entry point is the Debug button on the tester toolbar. Hit it, and the debugger opens with the trace already recorded. The demo pattern <([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+/>) against <div>Content</div> generates 7,530 execution steps for 18 characters of input. That gap between "looks simple" and "7,530 steps" is exactly what the debugger makes visible.

Regex debugger overview

The execution graph

The graph is a flame-chart of the engine's work. Each bar is one node attempt — a TRY that ended in OK (green), FAIL (red), or was undone by backtracking (thin blue give-back marks). Rows are stack depth; vertical bands separate match attempts.

Execution graph

It behaves like a proper profiler view: scroll-wheel zoom, drag to pan, double-click to zoom to a block, click any block to jump there — the amber marker follows, and every other panel updates in lockstep. A healthy trace reads as a calm staircase. A sick one shows the signature immediately: one region of the graph orders of magnitude wider than everything else, striped with red and blue.

Hot spots: where the steps go

The Hot spots table is the profiler summary: every node of your pattern, ranked, with its operation count, give-backs, failures, and share of total steps.

Hot spots table

This is the same workflow you'd use on slow code: profile, find the hot function, fix, re-profile. It just didn't exist for regular expressions in a browser before.

From diagnosis to fix in two clicks

Diagnosis without a fix is only half a tool. The Suggestions panel combines static analysis with trace statistics to propose concrete rewrites — dialect-aware, so a PCRE2 pattern gets atomic groups and possessive quantifiers while a JavaScript pattern gets the constructs ES2025 actually supports.

The classic case: (a+)+b on aaaaaaaaaaX — the textbook catastrophic backtracking example. The debugger names the disease and prescribes the cure:

Suggestions panel

Two things make this safe to trust:

  1. Equivalence checking — both patterns run against the real engine for your dialect on your input; only identical match lists earn the "same matches on this input" badge. A faster regex that matches less is a bug, not an optimization.
  2. Apply writes the rewrite into the tester and re-traces immediately: same input, Healthy verdict, 404 steps instead of 50,000+.

After applying the rewrite

A tracer kept honest

The trace comes from an instrumented backtracking engine — a continuation-passing matcher written for this purpose, speaking the semantics of nine flavor dialects. Every trace is cross-checked against the real thing: the match outcome is compared with the actual engine for the selected flavor (native RegExp, or PCRE2/.NET compiled to WebAssembly), and a "trace verified" badge only appears when they agree.

Try it: regextester.dev/debug — or read the full write-up at regextester.dev/blog/regex-debugger. Free, no signup, everything runs locally.

Top comments (0)