ToolBoxOnline
Developer

Your Regex Is Freezing: Catastrophic Backtracking, Explained

A regex that flies through most strings and hangs on one is usually doing the same work twice. Here's how catastrophic backtracking happens — and the pattern to spot before it eats your page.

regexbacktrackingReDoSperformanceregular expressions

You've got a regex that validates usernames. It's been fine for months. Then one afternoon someone pastes a long run of as into the form and the page hangs for ten seconds. The regex didn't get slower — it hit a pathological input, and the engine started repeating the same failed work. This is catastrophic backtracking, and it's the most common way a "fast" regex becomes a server-killer.

When a Regex Does the Same Work Twice

Regex engines match by trying and failing, and when a match fails the engine doesn't give up — it backtracks to the last choice point and tries another path. That's normal. Trouble starts when two quantifiers can match the same text. A classic is (a+)+$ on a string of as followed by an X: the outer + can split the group in a dozen ways, and for each split the inner + re-checks the same characters. Paths grow exponentially with input length, so 20 characters take milliseconds and 40 take minutes. Same pattern, same task — just exponentially more work.

The string that breaks you often looks innocent — not a huge document but a long run of a single character, or an alternation like (ab|a)+ where both branches can match the same prefix. The engine dutifully explores every dead end.

Spotting the Time Bomb (and Diffing the Fix)

Two signs tell you a pattern is fragile. First, nested quantifiers — anything like (x+)+ or (x|y)+ where the same text can be consumed in more than one way. Second, alternations whose branches overlap at the start, like (ab|a)*. When you see either, test it on a long, near-match input before it ships. The fix is usually to restructure so the engine never retries: possessive quantifiers like a++, or an atomic group that commits once it matches. Often you can flatten the whole thing into a simpler token pattern that matches in one pass.

Paste the suspicious pattern into a regex tester and try it against a deliberately hostile string — if the match time balloons, you've found your bomb before your users did. Compare the broken pattern and your fix with a text diff on the same sample input to see which construct changed the behavior. And keep the final regex readable in your source with a code formatter so the next person can review the logic instead of squinting at one long line.

One Rule That Prevents Most of It

The habit that stops this class of bug: whenever you write a quantifier inside a group that's also quantified, pause and ask whether the engine could match the same characters more than one way. We covered the greedy-versus-lazy side in our guide to lazy and greedy quantifiers; catastrophic backtracking is what happens when the two get stacked. If a pattern ever makes you nervous, test it on the ugliest input you can invent.

Tools mentioned in this article

Share this tool