Regex testen
Regex Tester matches a pattern against a subject in your browser, inside a Web Worker with a two-second timeout. That is not caution for its own sake: JavaScript offers no way to interrupt a running regular expression, so a pattern like (a+)+$ against a long input holds its thread indefinitely, and killing the worker is the only defence that actually works.
Die Oberfläche dieses Tools ist auf Englisch.
Die folgende Anleitung ist nur auf Englisch verfügbar.
How does Regex Tester work?
Most regex testers run the pattern on the page's main thread. That works until someone pastes a pattern that backtracks, at which point the tab stops responding and the only remedy is to close it.
Catastrophic backtracking, and why it cannot be caught
JavaScript's regex engine backtracks: when a match fails it retries every alternative path. For (a+)+b against a run of a characters ending in anything else, the paths are every way of dividing the run between the two quantifiers — which is exponential. Thirty characters is about a billion attempts. There is no timeout option, no abort signal, no yield point and no exception; the engine simply does not return.
This is the ReDoS vulnerability class, and it takes down real systems. Cloudflare's 2019 global outage was one regex in one WAF rule; Stack Overflow's 2016 outage was a pattern matching whitespace on a post with a long run of it. Both were on a server, where the consequence is every request blocking rather than one tab.
A worker can be killed, which is the entire design
So the match runs in a Web Worker and a timer terminates it after two seconds. That is the only mechanism available: nothing can stop a regex mid-execution, but a thread can be destroyed. When the timeout fires you get a clear message naming the likely cause instead of a frozen tab — which is also a useful signal, because a pattern that times out here is a pattern that would hang a server.
Static analysis names shapes, not verdicts
The risk panel looks for nested quantifiers, quantified alternations with overlapping branches, and adjacent quantifiers over the same characters. Those account for nearly every real incident. What it deliberately does not do is declare a pattern safe: deciding whether an arbitrary regex has exponential worst-case behaviour is a genuine analysis problem, and a tool that promised safety would be making a promise it cannot keep.
Zero-length matches need a nudge
A global match loop over a pattern that can match nothing — a*, (?:), \b — never advances lastIndex and loops forever. Every correct implementation increments it manually after an empty match, and this one does; it is a small thing that breaks a surprising number of tools.
The breakdown is per token
The pattern is split into its tokens with a plain-language note for each, because the usual problem with a regex someone else wrote is not that it is wrong but that it is unreadable. Runs of literal characters are grouped rather than listed one by one.
Pattern
(\w+)@(\w+\.\w+)
Matches
1 ada@example.com at 58 $1 ada $2 example.com 2 grace@example.org at 271 $1 grace $2 example.org
What options and edge cases does Regex Tester support?
| Parameter | Type | Default | Behaviour & edge cases |
|---|---|---|---|
| g — global | flag | on | Find every match rather than the first. Without it, only one result is returned however many exist. |
| i — ignore case | flag | off | Case-insensitive. With the u flag it also applies Unicode case folding, which handles more than ASCII. |
| m — multiline | flag | on | ^ and $ match at every line break rather than only at the ends of the subject. Almost always what you want for log-shaped input. |
| s — dotAll | flag | off | Makes . match a newline too. Without it, .* stops at the end of a line, which is the usual reason a multi-line match fails. |
| u — unicode | flag | off | Enables \u{…} escapes and \p{…} property classes, and makes the engine work in code points rather than UTF-16 units — which is what makes . match a whole emoji. |
| y — sticky | flag | off | Match only at lastIndex, with no scanning forward. Used for tokenisers, where a gap means a syntax error rather than something to skip. |
| Timeout | milliseconds | 2000 | The worker is terminated after this. There is no way to distinguish a slow pattern from an exponential one in advance, so elapsed time is the only signal available. |
| Match limit | count | 5000 | Results are truncated past this, and the truncation is reported. Rendering fifty thousand rows helps nobody. |
| Replacement | $1, $<name>, $& | — | Numbered groups, named groups and the whole match. $$ is a literal dollar sign, which is the one everybody forgets. |
Frequently asked questions
Why did my pattern time out?
It is almost certainly backtracking catastrophically. Look for a quantifier inside a quantified group — (a+)+, (\d*)* — or a quantified alternation whose branches can match the same text, like (a|ab)+. The engine tries every way of dividing the input between them, which is exponential in the input length. The risk panel names the shape if it can find one.
How do I fix a pattern that backtracks?
Remove the ambiguity about which part matches what. Replace (a+)+ with a+. Make alternation branches mutually exclusive so only one can match at each position. Anchor the pattern, so a failure does not retry from every character. Bound open-ended repetitions to what the data actually contains. And prefer a specific character class over a wildcard: [^"]* backtracks far less than .*.
Is ReDoS a real risk or a theoretical one?
Real, and expensive. Cloudflare's global outage in July 2019 was a single regular expression in a WAF rule; Stack Overflow went down in 2016 on a pattern matching trailing whitespace when a post contained a long run of it. On a server the failure mode is worse than here — every request queues behind the one stuck in the engine, so one crafted input takes down the service.
Why is my .* not crossing lines?
Because . does not match a newline unless the s flag is set. This is the single most common regex surprise in JavaScript, and it is easy to mistake for the m flag — but m changes what ^ and $ mean, not what . matches. For a multi-line match you want s; for line-anchored matching you want m; they are independent and often both.
Should I validate an email address with a regex?
Only loosely. The RFC 5322 grammar for an address runs to several hundred characters as a pattern, accepts things nobody expects — quoted local parts, comments, IP-literal domains — and still cannot tell you whether the mailbox exists. The useful pattern checks for an @ with something either side and a dot in the domain; the actual validation is sending a confirmation email.
Is my pattern or subject uploaded?
No. The match runs in a Web Worker inside your browser — a separate thread in the same tab, not a server. You can confirm it in the Network panel. It matters because the subject text people test against is usually a production log, and production logs contain email addresses, tokens and customer identifiers.