Regex Tester

Test a pattern against real text

Write a pattern, paste something to run it against, and the matches are highlighted in place with every capture group listed underneath. Add a replacement and the rewritten text appears as you type.

The pattern and flags live in the page address so a working expression is easy to send to someone. The text you test against does not — it is usually real data, and that belongs in the page rather than in a link.

Why this one cannot freeze your tab

Most online regex testers can, and it is worth explaining because it is the least obvious hazard in this whole category of tool.

JavaScript’s regex engine — like Perl’s, Python’s, Java’s and .NET’s — works by backtracking. When a match fails partway through, it goes back and tries a different division of the text. Usually that is a handful of attempts. Sometimes it is astronomically more.

The classic case is a quantifier inside a quantifier:

(a+)+$ against aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!

There are exponentially many ways to split those as between the inner and outer +, and because the ! means nothing will ever match, the engine tries all of them. Thirty characters takes milliseconds. Forty takes seconds. Fifty takes longer than you are going to wait — and each extra character doubles it.

There is no way to interrupt a running regex from JavaScript. On the main thread it holds everything: no repaint, no scrolling, no way to clear the field, no error message. The tab is simply gone, and on a phone the browser eventually kills the page. The visitor did nothing unusual — they typed a pattern, which is the entire point of the tool.

So the match here runs in a Web Worker, a separate thread, with a time limit. If it takes too long the thread is terminated outright and you get a message explaining why. Your tab keeps working throughout.

It is worth trying: paste a run of as ending in !, type (a+)+$, and watch it get stopped instead of taking the page with it.

Why patterns cannot just be screened

A reasonable question: why not detect the dangerous ones and refuse?

Because you cannot. Deciding whether a pattern backtracks catastrophically on some input is not something you can determine by looking at it — plenty of nested quantifiers are harmless, and plenty of slow patterns look nothing like the classic shape. The tool does point out nested quantifiers when a timeout happens, but that is an explanation after the fact, not a gate. The time limit is the thing that actually protects you.

The same reasoning applies to your own code. If a regex ever runs against input a user controls — a form field, a URL, a log line — it is a denial-of-service vector. This is a real and repeatedly exploited class of bug, catalogued as ReDoS, and it has taken down production systems at Cloudflare and Stack Overflow among others.

Avoiding the problem in your own patterns

Three habits cover most of it.

Do not nest quantifiers. (a+)+, (a*)*, (\d+)* — if a quantified group is itself quantified, look for another way. Usually a+ alone does the same job.

Make alternations disjoint. (a|a)+ is pathological because both branches match the same thing, so the engine has two ways to consume every character. (cat|car) is fine; (\w+|\d+) is not.

Be specific instead of using .*. "[^"]*" is both faster and safer than ".*?", because a negated character class cannot backtrack into territory another part of the pattern also wants.

And where the language offers them, atomic groups and possessive quantifiers solve this directly by forbidding backtracking. JavaScript has neither, which is part of why the problem is so common in web code.

The flags

g — global. Find every match rather than stopping at the first. Almost always what you want here.

i — ignore case.

m — multiline. Makes ^ and $ match at the start and end of each line instead of the whole string. This is the one people expect to be on by default and it is not.

s — dotAll. Lets . match a newline, which it otherwise never does. The cause of a great many patterns that work on one line and fail on two.

u — unicode. Enables proper handling of characters outside the basic plane and turns on \p{…} property escapes. Without it, an emoji is two separate code units and . matches half of one.

y — sticky. Each match must begin exactly where the previous one ended. Useful for tokenizers, confusing everywhere else.

Which flavor this is

JavaScript’s, run by your own browser. That is the point: what happens here is exactly what your code will do, rather than an approximation from a server running a different engine.

If you are porting a pattern from elsewhere, the differences that actually bite:

No atomic groups or possessive quantifiers. (?>…) and a++ are PCRE features JavaScript does not have — and, as above, they are the direct fix for backtracking.

Lookbehind is newer than you think. (?<=…) works in current browsers but was missing from Safari until 2023, so it is still worth avoiding in anything with a long tail of users.

Named groups use (?<name>…), referenced as $<name> in a replacement and \k<name> inside the pattern.

\d and \w are ASCII-only regardless of the u flag. For real Unicode digits or letters you need \p{Nd} and \p{L}, which require u.

Replacements

$1 to $9 insert numbered capture groups, $<name> inserts a named one, $& is the whole match and $$ is a literal dollar sign. A group that did not participate in the match inserts nothing rather than the word “undefined”.

One implementation detail with a reason behind it: the replacement is built from the matches already found, not by running the pattern a second time. Running it again would be a second opportunity to hang — on a thread that cannot be killed — for no benefit.

Reading the output

Matches are highlighted in the text with alternating colors, so two adjacent matches are visibly two rather than one long one. The table below lists each with its position and capture groups; it stops at 200 rows, because a table of thousands costs more to draw than the match did to run.

Zero-length matches — from patterns like ^ with the m flag, or \b — are counted but not highlighted. There is nothing to highlight, and marking them would put empty boxes between every character.

For comparing two blocks of text rather than pattern-matching one, use the text diff checker. For inspecting a JSON payload, the JSON formatter.

Frequently asked questions

Which flavor of regex is this?

JavaScript's, run by your own browser — so what you see here is exactly what your code will do. Patterns written for PCRE, Python or .NET mostly work, but not lookbehind in older Safari, and not possessive quantifiers or atomic groups, which JavaScript does not have at all.

Why did my pattern get stopped?

It hit the time limit, which almost always means catastrophic backtracking. A pattern with nested quantifiers such as (a+)+ can take exponential time on input that nearly matches: 30 characters is milliseconds, 40 is seconds, 50 is longer than you will wait.

Can a regex really freeze a browser tab?

Yes, completely — no repaint, no scrolling, no way to clear the field — and there is no way to interrupt a running regex from JavaScript. That is why the match here runs in a background thread that can be terminated. Most online testers run it on the main thread and simply hang.

How do I use capture groups in a replacement?

$1 to $9 for numbered groups, $<name> for named ones, $& for the whole match and $$ for a literal dollar sign. The replacement preview updates as you type.

What do the flags do?

g finds every match instead of the first. i ignores case. m makes ^ and $ match at each line rather than only the whole string. s lets . match newlines. u enables full Unicode handling. y anchors each match to where the last one ended.

Is my test text uploaded?

No. The pattern and flags go in the page address so a pattern is easy to share, but the text you test against stays in the page and is never put in the URL — it is usually somebody's real data.

Do I include the slashes?

No. Write the pattern on its own — \d{4}-\d{2}, not /\d{4}-\d{2}/g — and set the flags with the checkboxes.