Blog

Regex, and Why It Sometimes Hangs

2 September 2026

Most regex advice is a table of symbols. That is the easy half. The half that actually causes production incidents is what the engine does with those symbols — because a pattern that looks harmless can, on the right input, take longer to finish than the universe has existed. That failure has taken down real services, and understanding it takes about five minutes.

The engine guesses, then takes it back

A JavaScript regular expression is evaluated by backtracking. The engine walks the pattern making the most optimistic choice available at each step, and when a choice turns out not to work, it rewinds to the last decision point and tries the next option.

Match a.*b against axxbxxb: the .* greedily swallows the whole string, then the engine needs a b, finds it has run out of text, and hands characters back one at a time until a b turns up — the last one. That give-and-take is backtracking, and normally it costs nothing worth measuring.

When backtracking explodes

It stops being free when one quantifier sits inside another. Consider (a+)+$ against thirty a characters followed by a !.

The inner a+ can take one a, or two, or all thirty; the outer + then repeats that choice. So a run of thirty as can be divided between the two quantifiers in over a billion distinct ways — and every one of them matches the as perfectly well. The pattern only fails at the very end, when $ meets the !. Since failure is at the end, the engine dutifully tries every single split before concluding the string does not match.

Add one more a and the work doubles. This is catastrophic backtracking, and it is the mechanism behind a whole class of denial-of-service bugs: an attacker who can influence the text a server matches can hang a thread with a few dozen characters. It is why our tester runs your pattern in a background thread with a two-second limit — the page stays usable and you get told what happened, rather than a frozen tab.

The fix is to remove the ambiguity. If only one way to split the text exists, the engine has nothing to backtrack through: make the inner element unambiguous, drop the nesting, or anchor the pattern so failure is detected early.

Greedy versus lazy

The everyday version of the same idea. * and + are greedy: they take everything they can and give back only under pressure. Add ? and they become lazy, taking the minimum and expanding only as needed.

The canonical demonstration is HTML. Against <b>hi</b>, the pattern <.*> matches the entire string — the greedy .* runs to the end and backs up to the final >. Write <.*?> and you get <b> and </b> as separate matches. Whenever a match is much larger than you expected, greed is the first thing to check.

There is no such thing as "regex"

Regular expressions are a family of languages that resemble each other. JavaScript, PCRE, Python, Java, Go and POSIX all differ — sometimes in features, occasionally in what identical syntax means.

The deepest split is architectural. Go and Rust use engines that guarantee linear time, so catastrophic backtracking cannot happen at all; the price is that backreferences and lookaround do not exist there, because those features are what make the guarantee impossible. PCRE goes the other way, adding recursion, atomic groups and possessive quantifiers. Our tester runs the JavaScript flavour — the one your browser and Node actually execute — so lookbehind, named groups and unicode escapes work, and PCRE-only constructs do not.

The email address question

Every regex discussion arrives here eventually. The honest answer is that you should not validate an email address with a regular expression, beyond checking there is an @ with something on both sides.

RFC 5322 permits quoted local parts, comments in parentheses and nested structures; the well-known "complete" pattern is thousands of characters long and still gets edge cases wrong. Worse, a syntactically perfect address may not receive mail, and that is the thing you actually care about. Sanity-check the shape, then send a confirmation link. That verifies format and deliverability at once, which no pattern can do.

FAQ

Why does my regular expression freeze the page?

Almost always catastrophic backtracking. When a pattern contains a quantifier inside another quantifier, such as (a+)+, the engine can split the same text between the two in exponentially many ways. On a string that nearly matches but fails at the very end, it tries every one of those splits before reporting failure. Thirty characters can mean over a billion attempts. The fix is to remove the nesting or make the inner part unambiguous, so only one split is possible.

What is the difference between greedy and lazy quantifiers?

A greedy quantifier such as .* takes as much text as it can and then gives characters back until the rest of the pattern fits. A lazy one, written .*?, takes as little as possible and adds characters only as needed. The classic symptom of the difference is matching HTML tags: <.*> against two tags matches everything from the first opening bracket to the last closing one, while <.*?> matches each tag separately. When a match is far larger than expected, a greedy quantifier is usually the cause.

Why does my regex behave differently in another language?

Because there is no single regex language. JavaScript, PCRE, Python, Go and POSIX differ in what they support and occasionally in what the same syntax means. Go and Rust use engines that guarantee linear time but drop backreferences and lookaround entirely. PCRE adds recursion, possessive quantifiers and atomic groups that JavaScript does not have. Our tester runs the JavaScript flavour, the one your browser and Node execute.

Should I use a regular expression to validate an email address?

For anything beyond a basic sanity check, no. The grammar in RFC 5322 permits quoted strings, comments and nested structures that a readable pattern cannot cover, and the well-known complete pattern runs to thousands of characters. Checking that there is an at sign with something either side, then sending a confirmation message, verifies both the format and that the address actually receives mail — which is the thing you really wanted to know.

Related guides

Try it now: Free Regex Tester

Tools from this guide

Regex Tester Match as you type, with capture groups and flags.