Regex tester · Guide

Regex Backtracking Tester: Finding Catastrophic Patterns

Most regex engines backtrack: when a path fails they rewind and try another. Usually that costs microseconds. With the wrong pattern shape it costs exponential time, and a 30-character input can pin a CPU core for years.

The shape to look for

The danger sign is a quantifier applied to a group that itself contains a quantifier, where the inner parts can match the same text — nested quantifiers with ambiguity:

Regex
(a+)+$          # classic — exponential on "aaaaaaaaaaaaaaaaaaaaX"
(\s*\w+)*$      # same shape, looks harmless
(\d+|\w+)+$     # alternation that can match the same text
^(\w+\s?)*$     # very common in "validate a name" patterns

On failure the engine must try every way of splitting the input between the inner and outer quantifiers. That count doubles with each added character.

Why it only fails sometimes

A matching input returns quickly, because the engine stops at the first success. The explosion happens on inputs that almost match and then fail at the end — which is why these patterns pass tests and die in production.

To test one, feed it a long repetition of the character it accepts plus one character it cannot: "a" * 30 + "X". If the tester hangs, the pattern is unsafe for untrusted input.

Fixing it

Remove the ambiguity so only one split is possible. A negated character class is usually both faster and clearer than a lazy quantifier:

Regex
(a+)+$        ->  a+$
".*?"         ->  "[^"]*"
^(\w+\s?)*$   ->  ^\w+(?:\s\w+)*$

Where the engine supports them, atomic groups (?>...) and possessive quantifiers a++ forbid backtracking into a section entirely — available in Java, PCRE and .NET, not in JavaScript or Python.

Engine-level protection

.NET accepts a timeout on the Regex constructor, which converts a hang into a catchable exception. Go and Rust use RE2-style engines with no backtracking at all, so they are immune by construction — at the cost of no lookarounds or backreferences.

JavaScript, Python, Java, PHP and Ruby all backtrack with no built-in limit. If a pattern in those languages touches user input, review its shape, and never build a regex from user-supplied text.

Frequently asked questions

How do I know if a pattern is vulnerable?

Look for a quantified group containing another quantifier where both can match the same characters, then test with a long almost-matching string.

Does a lazy quantifier fix backtracking?

No. Lazy changes the order of attempts, not their number. Remove the ambiguity instead.

Which languages are immune?

Go and Rust use RE2-style linear engines. Most others backtrack without limit.

Ready to try it?

Open the free browser-based Regex tester and apply what you just read — no sign-up, runs locally.

Open the Regex tester tool