Regex Tester
Test a pattern against real text, with matches highlighted and a hard stop on runaway backtracking.
Why a regex tester needs a worker
Most of the machinery on this page exists for one failure mode. Consider /(a+)+b/ matched against thirty as and no b. The engine has to decide how to split those thirty characters between the inner and outer quantifier, and there are 230 ways to do it. Every one is tried before the match can be declared impossible.
The important part is that a running regex cannot be interrupted. JavaScript is single-threaded, so a setTimeout guard cannot fire until the regex returns β which for that input is somewhere past the heat death of the universe. The tab is gone.
So the pattern runs in a Web Worker, on its own thread, and the main page holds a timer. When the timer wins, worker.terminate() kills the thread outright. That is the only mechanism that actually works, and it is why this tester tells you your pattern is dangerous instead of becoming a hung tab.
Worth internalising, because the same pattern in production code is a denial-of-service: user-supplied input matched against a vulnerable regex, and one request pins a CPU core indefinitely. The class of bug is called ReDoS.
The shapes to avoid
| Dangerous | Why | Safer |
|---|---|---|
(a+)+ | Nested quantifiers β exponential splits | a+ |
(\w|\s)* | Alternation inside a quantifier, overlapping branches | [\w\s]* |
(\d+)*$ | Quantified group with an anchor forcing full exploration | \d*$ |
.*.*= | Two unbounded wildcards competing for the same text | [^=]*= |
The common factor is ambiguity: more than one way for the pattern to consume the same characters. A character class does the job of an alternation without the ambiguity, which is why the safer column is mostly just replacing | with [].
Greedy, lazy, and the bug everyone writes once
Quantifiers are greedy by default: .+ takes everything it can and hands characters back only under protest. Against <b>bold</b>:
| Pattern | Matches |
|---|---|
<.+> | <b>bold</b> β the whole thing |
<.+?> | <b> β lazy, stops at the first > |
<[^>]+> | <b> β and faster, because there is nothing to backtrack |
The third form is the one to reach for. A negated character class cannot overshoot, so there is no backtracking to undo β it is both clearer about intent and cheaper to run than the lazy version.
lastIndex, and why every other match disappears
A RegExp with the g flag is stateful. It keeps a lastIndex that advances after each exec or test, so reusing one object across separate calls resumes from wherever it stopped:
const re = /\d+/g;
re.test('123'); // true, lastIndex is now 3
re.test('123'); // false! it resumed from index 3Create the regex inside the loop, reset lastIndex = 0, or use matchAll, which manages it for you. The tester above creates a fresh object for every run, so what you see here is the first-call behaviour.
Where JavaScript regex differs
Results here match Node and browser code exactly, because it is the same engine. Against other languages, the differences that catch people out:
- No atomic groups or possessive quantifiers. PCRE has
(?>...)anda++to prevent backtracking outright; JavaScript has neither, which is why ReDoS is easier to write here. - No recursion. You cannot match balanced brackets in JavaScript regex. If that is the requirement, you need a parser.
- Lookbehind arrived late.
(?<=...)is supported in current browsers but not in older Safari, so check your targets. - Goβs RE2 is a different machine. Linear time guaranteed, no backreferences, no lookaround. If you have ever wondered why Go dropped features, this page is the reason.
Related
If the text you are matching against is JSON, formatting it first usually makes the pattern unnecessary β a parser beats a regex on structured data every time. For scheduling rather than matching, the cron parser is the neighbouring tool.
Regex questions
Why did my pattern get stopped after two seconds?
Because it was backtracking catastrophically. Certain shapes β nested quantifiers like (a+)+ or (\w|\s)* β make the engine try an exponential number of ways to match, so a string of forty characters can take longer than the universe has existed. There is no way to interrupt a running regex from JavaScript, which is why the pattern runs in a Web Worker here: the worker can be terminated, and the page stays responsive. A timeout on the main thread would only fire once the regex finished, which for those patterns is never.
Does this use the same regex engine as my code?
It uses your browser's, which is the JavaScript engine β so results match Node and browser code exactly. They will not always match PCRE, Python, Go or Java. JavaScript has no atomic groups, no possessive quantifiers and no recursion; lookbehind is supported in current browsers but arrived late. Go's RE2 is a different design entirely and deliberately has no backreferences, which is precisely how it avoids the backtracking problem above.
Why does my global regex only find every other match?
A RegExp object with the g flag carries a lastIndex that advances with each call, so reusing the same object across separate exec or test calls resumes from where it left off. It is one of the most reliably confusing parts of the API. Either create the regex fresh each time, or reset lastIndex to 0 before use, or use matchAll which handles it for you.
What is the difference between a greedy and a lazy quantifier?
A greedy quantifier takes as much as it can and gives characters back only when forced; a lazy one (written with a trailing ?) takes as little as possible and expands only when it has to. Against <b>bold</b> the pattern <.+> matches the whole string, because .+ swallows everything and backtracks just enough to find a final >. Writing <.+?> matches only <b>. This is the single most common reason a pattern matches far more than intended.
Is it safe to paste real data in here?
Yes. The pattern and the text are handed to a worker inside this page and evaluated there. Nothing is sent to a server β which is also what makes cancelling a runaway pattern possible, since there is no request to wait on.
Last reviewed . Found something out of date? Tell us.
