Regular expressions

Regex tester: test a regular expression online

Type the expression and watch the matches highlighted over your text instantly, groups and all. And, above all, with a panel that explains your own expression back to you, piece by piece, in plain language.

Zero uploads · all in your browser

Neither the expression nor the test text leaves your device.

Why it matters that the test text is not uploaded

What people paste into a regex tester is rarely a made-up example: it is a slice of a production log, an export with customer data, the body of a real response or a list of email addresses. It gets pasted precisely because the real case is what tells you whether the pattern works. Here it stays in your tab: even the separate thread where expressions run lives inside the same page.

How it works, in 3 steps

  1. Write the expression

    Or pick one from the library: each comes with sample text containing cases that match and cases that do not, so the difference is visible at a glance.

  2. Watch the highlighting

    Matches are painted over the text as you type. Below, each one with its position and its groups, numbered and named.

  3. Understand it, or replace with it

    The explanation tab takes the expression apart piece by piece. The replace tab applies a replacement with group references over the whole text.

What people use it for

Catastrophic backtracking, and why this page does not freeze

There is a way of writing regular expressions that can take down an entire service, and most of the people writing them have never heard of it. It happens when something that repeats sits inside something else that also repeats: (a+)+, (\d+)*, (\s*\w+)*. While the text matches, nothing goes wrong. The trouble arrives with text that almost matches and fails at the end: the engine then tries every possible way of splitting those characters between the two repetitions, and the count is exponential.

The number is startling: with (a+)+$ and thirty characters, in the test we ran while building this tool the engine took over ten seconds. With forty it is hours. And this is no laboratory curiosity: it is a routine cause of real outages, because all it takes is somebody sending the right string to a form validated with such a pattern.

Here it is tackled from two sides. First, the shape of the expression is analysed before running it and you get a warning pointing at the guilty fragment — without nagging about ordinary expressions: the email, date and IP patterns raise nothing. And second, the part that actually protects you: searches run in a separate browser thread. While a regex engine is working, the thread running it is completely blocked: no timer and no cancel button ever gets to run. A separate thread, on the other hand, can be killed from outside, and that is what happens if it does not answer within a little over a second. Any page promising a time limit without running in a separate thread simply does not have one.

It is the JavaScript engine, and that matters

This tool uses the browser's regex engine, which is JavaScript's. Almost everything you test here will behave the same elsewhere, but there are differences that bite exactly when you copy the pattern somewhere else:

  • Possessive quantifiers (a++) and atomic groups ((?>…)) exist in Java, PCRE and .NET, but not in JavaScript. If you see them in a pattern you were given, they do not mean the same thing here.
  • Flags are written separately. What are buttons here is re.IGNORECASE in Python, or a (?i) at the start of the pattern.
  • \d and \w do not cover the same set. In Python, \d includes digits from other scripts by default and \w includes accented letters; in JavaScript they are ASCII-only unless you use the u flag with \p{…}.
  • Named groups are (?<name>…) here and in .NET, but (?P<name>…) in Python.
  • Variable-length lookbehind works in .NET and modern JavaScript, but Python's re module rejects it.
  • The global flag carries state. In JavaScript an expression with g remembers how far it got between calls, so reusing the same object in a loop gives alternating results that look random. It is the quirk that causes the most grief.

Quick syntax reference

You writeIt meansExample
.any character except a line breaka.c matches “abc”, “a c”
\d \w \sdigit · letter, digit or underscore · whitespace\d\d matches “42”
\D \W \Sthe opposite of the above\S+ a run without spaces
[abc]one of those characters[aeiou] a vowel
[^abc]anything but those[^0-9] something that is not a digit
[a-z]a range[a-zA-Z0-9]
* + ?zero or more · one or more · optionalcolou?r matches both spellings
{2} {2,} {2,5}exactly · at least · between\d{5} a postcode
+? *?lazy: the least, not the most<.+?> one tag, not the whole line
^ $start and end (of each line with the m flag)^\d+$ digits only
\bword boundary\bcat\b does not match “category”
(…)a group that is captured(\d{4})-(\d{2})
(?:…)groups without capturing(?:ab)+
(?<n>…)a named group(?<year>\d{4})
a|bone thing or the othercat|dog
(?=…) (?!…)what follows must be there · must not be(?=.*\d) there is a digit somewhere
\. \/the literal character, without its meaning\. an actual dot

The explanation is what turns this into learning

Testing an expression tells you whether it works. It does not tell you why. That is why the explanation tab takes the pattern apart piece by piece and turns each one into a sentence: what \b is, what a group stores, how + differs from +?, what that lookahead you copied actually does. Pieces are indented by how deeply nested in parentheses they are, so the structure is visible as well as readable.

That breakdown rests on a parser of our own, and a parser is easy to over-trust: if it skips a fragment, the explanation would lie with total confidence. The check that prevents it is simple and harsh: joining the text of every piece must give back exactly the original expression. If a single character is missing or spare, the analysis is wrong. It is verified against the whole library and against three thousand randomly generated expressions.

On validating email with a regular expression

It is the number one request and it deserves a warning. The standard defining email addresses allows things nobody expects: quotes, comments in parentheses, IP addresses in square brackets. The expression that accepts them all runs to thousands of characters, and even then it does not check the only thing that matters: whether the mailbox exists.

The sensible move — and what the library pattern does — is to stay simple: there is an at sign, there is something before it, there is something after it with a dot. Then confirm for real by sending an email with a link. Rejecting a customer's valid address annoys people far more than accepting a fake one that will never be confirmed.

If what you need is to tidy the text before running the expression over it — collapsing double spaces, rejoining lines broken by a PDF — that is what the case converter does, cleanup panel included. And if you are debugging an API response, the JSON formatter makes it readable first.

Frequently asked questions

What is catastrophic backtracking and why are you warning me?

It is how an expression can take down an entire program. It happens when something that repeats sits inside something else that also repeats, like (a+)+. With text that almost matches and fails at the end, the engine tries every possible split, and the count is exponential.

It is not theoretical: it is a routine cause of real outages, because all it takes is somebody sending the right string to a form validated that way. Here the shape is detected before running it, and the guilty fragment is pointed out.

Why does the tool not freeze on those expressions?

Because it does not run them on the page. While the engine is working, the thread running it is completely blocked: no timer and no cancel button ever gets to run.

So searches go to a separate browser thread, which can be killed from outside. If it does not answer within a little over a second, it is terminated and you are told the expression does not finish. Any page promising a time limit without doing this does not have one.

Will the expression I test here work in Python or .NET?

Often, but with traps. Possessive quantifiers and atomic groups exist in Java, PCRE and .NET but not in JavaScript. Python writes flags separately. \d and \w cover more by default in Python.

Named groups are (?<n>…) here and (?P<n>…) in Python. And variable-length lookbehind is rejected by the re module. Always test the pattern in the destination too.

Why does my email regex reject valid addresses?

Because validating email with a regex is uglier than it looks: the standard allows quotes, comments and bracketed IP addresses. The complete expression runs to thousands of characters and still does not check whether the mailbox exists.

The practical route is to stay simple — there is an at sign, something before it, something after it with a dot — and confirm by sending an email. Rejecting a real customer annoys people far more than accepting an address that will never be confirmed.

What is the difference between (…) and (?:…)?

The first groups and stores what matched, so you can see it in the table or use it as $1 when replacing. The second groups without storing anything.

Use the non-capturing one whenever you only need grouping: it keeps the other groups' numbers in place — avoiding the classic slip where $2 becomes $3 — and saves the engine work. And if the group does matter, name it with (?<name>…).

What does it mean for a quantifier to be lazy?

By default they are greedy: they take as much as they can. That is why <.+> over “<a> and <b>” matches the whole line instead of each tag.

Adding a question mark, <.+?>, takes the minimum and returns the two tags separately. It is the commonest fix when an expression captures too much.

Is my test text uploaded to a server?

No. The expression and the text stay in your browser; even the separate thread lives inside this page. No accounts, no database, and nothing survives a reload. Load the page, disconnect from the internet and check.

Why does the global flag give strange results in my code?

Because in JavaScript an expression with g remembers how far it got between calls. Reuse the same object with test in a loop and the second call starts where the first stopped: true, false, true, false.

It does not affect you here, because the tool resets that position on every pass, but it is worth knowing when you take the pattern into code.

Related tools