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
- How a diff actually works — another algorithm whose surprising output makes sense once you know what it optimises.
- Cron expressions, and the OR trap — a compact syntax with one rule that catches everyone.
- Every text case, and when to use it — the pattern-free way to reshape text.
Try it now: Free Regex Tester