How-to guide

How to Fix a Regular Expression That Hangs

A regex that hangs is backtracking catastrophically: nested quantifiers give the engine an exponential number of ways to split the input, and it tries them all before admitting failure. The signature is a quantifier inside a group that is itself quantified, such as (a+)+, combined with input that almost matches. Rewriting to remove the ambiguity fixes it.

Regex Tester

Test a regular expression and see every match highlighted.

Open the tool

The alarming property is how suddenly it appears. A pattern runs instantly on twenty characters and appears to hang forever on thirty, because each additional character can double the work. Nothing looks wrong in the pattern itself.

It only bites on input that nearly matches. A string that fails immediately is cheap to reject; a string that matches most of the pattern and then fails at the very end forces the engine to reconsider every earlier decision.

Step by step

  1. Look for a quantifier inside a quantified group

    Patterns shaped like (x+)+, (x*)*, or (x|xy)+ are the classic offenders. Each gives the engine more than one way to divide the same text, and the number of divisions grows exponentially with length.

  2. Test with input that almost matches

    Take a string that satisfies the pattern up to the final character, then break it. Add characters one at a time. If the time roughly doubles per character rather than growing gently, the pattern backtracks catastrophically.

  3. Make the alternatives mutually exclusive

    Rewrite so only one path can match any given character. Replacing (\d+|\w+)+ with \w+ removes the ambiguity entirely, because a digit is already a word character and the two branches were competing for the same input.

  4. Replace dot-star with a negated class

    Inside delimiters, "[^"]*" is dramatically faster and safer than ".*?" because it cannot cross the closing delimiter and so has nothing to backtrack over. This one substitution fixes a large share of slow patterns.

  5. Anchor the pattern

    Without an anchor, a failed match is retried at every starting position in the string, multiplying the cost by the input length. Adding ^ or \b where the match must begin turns many quadratic scans into linear ones.

Example

Both patterns validate the same thing. The first is exponential on failure; the second is linear.

Dangerous

^(a+)+$
matched against "aaaaaaaaaaaaaaaaaaaaaaaaX"

Safe rewrite

^a+$
same result, no backtracking

Frequently asked questions

What is catastrophic backtracking?
A regex engine exploring an exponential number of ways to match the same text. It happens when nested quantifiers make several paths able to consume the same characters, so a failure forces the engine to retry every combination before giving up.
Why does it only happen on some inputs?
Because a successful match stops at the first path that works, and an obviously wrong input fails immediately. The expensive case is input that matches almost all of the pattern and then fails, which forces every earlier choice to be revisited.
Is this a security problem?
It can be. If a pattern runs against user input, an attacker can supply a string designed to trigger the exponential case and hang the process. This is known as ReDoS, and it is why patterns applied to untrusted input deserve particular care.
Do all regex engines suffer from this?
No. Engines built on backtracking — including JavaScript, Python, Java, PCRE and .NET — are vulnerable. Automata-based engines such as Go's RE2 and Rust's regex crate guarantee linear time but drop features like backreferences and lookaround.
How do I test whether a pattern is safe?
Feed it a nearly-matching string and lengthen it one character at a time. Timing that roughly doubles with each character is the signature. A pattern that stays fast to a few hundred characters is almost certainly fine.

Tools used in this guide

All tools →
All guides →

Last reviewed .