Regex Tester
Build and debug regular expressions live. Type a pattern, toggle the flags, and every match
lights up in your test text with its numbered and named capture groups listed below. Switch on
replace mode to preview $1 substitutions, load a common-pattern preset, and keep the
cheatsheet handy. Uses the browser’s native JavaScript RegExp engine, so it behaves
exactly like your code — and nothing is ever uploaded.
Try a common pattern
Click a preset to load its pattern, flags and a sample string, then tweak it.
How to use the regex tester
- Type your pattern in the expression box — no surrounding slashes needed, just the body of the regex.
- Pick the flags you need. Keep
gon to see every match; addifor case-insensitive matching. - Paste your text into the test box. Matches highlight instantly, alternating colours so adjacent matches are easy to tell apart.
- Read the groups — each match lists its numbered and named capture groups so you can confirm you’re extracting the right pieces.
- Preview a replacement — switch on “Show replace” and use
$1,$<name>or$&to rebuild the text. - Copy the result and drop the pattern straight into your JavaScript, TypeScript or Node.js code.
Regex cheatsheet
| Token | What it matches |
|---|---|
. | Any character except newline (matches newline with the s flag). |
\d \D | A digit 0–9 / any non-digit. |
\w \W | A word character [A-Za-z0-9_] / any non-word character. |
\s \S | Any whitespace (space, tab, newline) / any non-whitespace. |
[abc] [^abc] | Any one of a, b, c / any character except a, b, c. |
[a-z] | A range — any lowercase letter. Combine ranges: [A-Za-z0-9]. |
^ $ | Start / end of the string (or of each line with the m flag). |
\b \B | A word boundary / a position that is not a word boundary. |
* + ? | 0 or more / 1 or more / 0 or 1 of the preceding token. |
{n} {n,} {n,m} | Exactly n / n or more / between n and m repetitions. |
*? +? ?? | Lazy (non-greedy) — match as few characters as possible. |
a|b | Alternation — match a or b. |
( ) | A numbered capture group. |
(?<name> ) | A named capture group, referenced as $<name>. |
(?: ) | A non-capturing group (groups without creating a capture). |
(?= ) (?! ) | Positive / negative lookahead. |
(?<= ) (?<! ) | Positive / negative lookbehind. |
\p{L} \p{N} | Unicode property escapes (letter / number) — needs the u flag. |
Tips for writing reliable regular expressions
- Escape special characters — a literal dot, plus or question mark must be written as
\.,\+,\?. Inside a character class, most metacharacters lose their meaning. - Prefer specific over greedy —
"[^"]*"reads a quoted string more safely than".*", which can swallow several strings on one line. - Anchor when you can — adding
^and$(or\bboundaries) prevents partial matches and helps the engine fail fast. - Use named groups —
(?<id>\d+)is far easier to maintain than counting parentheses for$1. - Watch nested quantifiers — patterns like
(a+)+can cause catastrophic backtracking; flatten them to a single character class. - Test the edge cases — empty input, multiple matches per line, and the longest realistic string. A good regex is the one that fails correctly, not just the one that passes once.
A short history of regular expressions
Regular expressions began as pure mathematics, not programming. In 1951 the American
logician Stephen Cole Kleene formalised a way to describe simple patterns in
sequences of symbols, calling them regular events (today we say regular
languages). His notation introduced the operator that still bears his name — the
Kleene star, written *, meaning “zero or more of the
preceding element.” That single idea — describing an unbounded set of strings with a
short, finite expression — is the foundation every regex engine rests on.
Regular expressions reached computers through Ken Thompson, a co-creator of
Unix. In the late 1960s he built Kleene’s notation into the QED text editor and later
into the Unix editor ed so users could search files by pattern. The most famous
descendant is grep, whose name comes directly from the ed
command g/re/p — “globally search for a
regular expression and print the matching
lines.” Thompson also published a 1968 algorithm for compiling a regular expression into
fast searching code, work that still underpins modern high-performance engines.
From there regex spread through the Unix toolchain — sed, awk,
lex — and then exploded in popularity with the Perl language,
which made powerful pattern matching a first-class feature. Perl’s dialect grew so
influential that in 1997 Philip Hazel created PCRE (Perl Compatible Regular
Expressions), a C library now embedded in PHP, the Apache and Nginx web servers and countless
other tools. In parallel, the POSIX.2 standard of 1992 defined two portable
flavours — Basic and Extended Regular Expressions — that Unix utilities still follow. The
engine in your browser, ECMAScript’s RegExp, is a separate lineage again,
standardised as part of JavaScript.
The theory behind the engine
A “regular” language is exactly one that a finite automaton — a machine with a fixed number of states and no extra memory — can recognise. Every regular expression can be converted into such a machine, and back again; the two are mathematically equivalent. Thompson’s construction turns a pattern into a nondeterministic finite automaton (NFA), which can then be transformed into a deterministic finite automaton (DFA) that reads each input character exactly once.
That theory explains why two very different families of regex engine exist.
Automaton (DFA-style) engines — used by grep, Google’s RE2 and the Rust
regex crate — guarantee matching in linear time no matter how nasty the pattern,
but cannot offer a few convenience features. Backtracking engines — used by
Perl, PCRE, Python, Java, .NET and JavaScript — explore the possibilities by trial and error,
which lets them support backreferences and lookaround, but at the cost of occasionally
catastrophic slowdowns (see below). When you test a pattern here you are driving a backtracking
engine, and understanding that is the key to writing patterns that stay fast.
Regex flavours: how JavaScript differs
There is no single regular-expression language — there are many closely related dialects, and a pattern that works perfectly in one can behave differently, or fail, in another. This tester runs the ECMAScript (JavaScript) engine, the same one in Node.js and every browser, so what you see here is exactly what your JavaScript or TypeScript code will do.
| Flavour | Where you meet it | Notable traits |
|---|---|---|
| POSIX BRE / ERE | grep, sed, shell tools | BRE needs \( for groups; ERE adds unescaped + ? |. |
| PCRE / Perl | PHP, Nginx, many editors | The richest dialect: atomic groups, possessive quantifiers, recursion. |
Python re | Python scripts | Inline flags and named groups written as (?P<name>…). |
| .NET / Java | C#, Java applications | Backtracking with extras; .NET supports per-match timeouts. |
| ECMAScript | This tool, browsers, Node.js | No atomic groups or possessive quantifiers; named groups use (?<name>…). |
JavaScript’s engine has matured quickly. The u (unicode) and y
(sticky) flags arrived in ES2015. ES2018 was a landmark release that added named
capture groups (?<name>…), lookbehind assertions
(?<=…) and (?<!…), the s (dotAll) flag, and
Unicode property escapes such as \p{L}. More recently
the d flag for match indices (ES2022) and the v flag for extended
Unicode sets (ES2024) were added. What JavaScript still lacks compared with PCRE are
possessive quantifiers (a++) and atomic groups
((?>…)) — the very tools other languages use to prevent runaway backtracking,
which is why the next section matters so much for JS developers.
Greedy, lazy and how matching proceeds
Understanding how the engine walks through your text turns regex from guesswork into a
predictable tool. By default every quantifier is greedy: *,
+ and ? grab as much text as they can, then give characters back one
at a time (that giving-back is backtracking) until the rest of the pattern can match. So
".*" applied to he said "one" then "two" matches the whole span from
the first quote to the last, because the greedy .* swallows everything before
relenting.
Append a ? to make a quantifier lazy (non-greedy):
".*?" matches as little as possible, stopping at the first closing quote, so it
captures just "one". Often the cleaner fix is a negated character
class: "[^"]*" says “a quote, then any run of non-quote characters,
then a quote,” which is both unambiguous and fast because it never has to backtrack.
Choosing greedy versus lazy versus a negated class is one of the most common decisions in
real-world patterns, and the live highlighting above makes the difference easy to see.
Catastrophic backtracking and ReDoS
Because JavaScript uses a backtracking engine, a careless pattern can take an
exponential amount of time on certain inputs — a genuine security flaw known as
regular-expression denial of service (ReDoS). The trouble appears when a
repeated group can match the same text in many overlapping ways and the overall match then
fails. The textbook example is (a+)+$ tested against a long string of
a characters followed by a character that cannot match: the engine tries every way
to split the as between the inner and outer +, and the number of
combinations roughly doubles with each extra character. A few dozen characters can lock up a
tab — or, on a server, an entire request thread.
Languages like Perl and PCRE defuse this with atomic groups and possessive quantifiers that forbid the engine from backtracking into a group. JavaScript has neither, so you have to design the danger out:
- Never nest quantifiers on overlapping text. Rewrite
(a+)+as a singlea+, and(\w+\s?)+as[\w\s]+. - Make repeated parts mutually exclusive. Turning a
(.*a)-style loop into([^a]*a)removes the overlap that fuels backtracking. - Anchor and be specific. Boundaries (
^,$,\b) and tight character classes let the engine fail fast instead of exploring dead ends. - Bound your input. Cap the length of strings you test untrusted patterns against, and prefer linear-time libraries (Google’s RE2, the Rust
regexcrate) for server-side matching of user-supplied patterns.
A useful rule of thumb: if a pattern feels slow in this tester, it will be slow — and exploitable — in production. The match counter here caps its iterations to keep the page responsive, but that safety net does not exist in your application code.
What regular expressions are good (and bad) at
Regex is the right tool for searching, validating, extracting and replacing flat, line-oriented text: finding every email-shaped string in a log, pulling dates out of a report, splitting a delimited row or swapping one token for another. It excels whenever the structure is local and predictable.
It is the wrong tool for anything deeply nested or recursive. The same theory
that makes regular languages fast also makes them unable to count arbitrary nesting — a finite
automaton has no memory to track how many brackets are still open. That is the real reason
behind the famous warning to never parse HTML with a regular expression: HTML, XML and
JSON are nested grammars, and a pattern that looks like it works will break on the first
comment, nested tag or edge case. Use a dedicated parser instead (the browser’s
DOMParser, or JSON.parse for JSON).
Email is the classic over-reach. A pattern that fully implements the RFC 5322 address grammar
is hundreds of characters long and still cannot confirm that an address actually receives mail.
In practice a simple sanity check such as [^@\s]+@[^@\s]+\.[^@\s]+ plus a real
confirmation email beats any monster regex. The lesson generalises: reach for regex when the
pattern is genuinely regular, and reach for a parser or a purpose-built library when it is not.
Lookaround and backreferences: matching in context
Two features let a pattern inspect its surroundings without consuming them. A
lookahead asserts that what follows does ((?=…)) or does not
((?!…)) match, and a lookbehind does the same for what precedes
((?<=…) and (?<!…)). For example, \d+(?= kg) matches
a number only when it is followed by “ kg”, yet the “ kg” itself stays
outside the match — handy when you want to find a value but keep its unit out of the captured
text. JavaScript has supported lookbehind since ES2018, so both directions work in this tester.
A backreference reuses what an earlier group captured. Inside the pattern,
\1 means “the same text that group 1 just matched,” so
(\w+)\s+\1 finds a doubled word like “the the”, and
\k<name> does the same for a named group. Backreferences are one of the
features a pure automaton engine cannot provide, which is precisely why JavaScript uses a
backtracking engine — and why the catastrophic-backtracking cautions above apply to anything
you ship.
Frequently asked questions
What regex syntax does this tester use?
It uses the browser’s native JavaScript (ECMAScript) regular-expression engine — the exact same RegExp implementation your code runs in Node.js, Deno and every modern browser. That means the patterns you test here behave identically in production JavaScript. JavaScript regex differs in small ways from PCRE (PHP/Perl), Python’s re, Java and .NET — for example JavaScript has no inline (?i) modifiers, no possessive quantifiers, and named groups use the (?<name>…) syntax. If you are writing for another language, double-check those edge cases, but for anything JS/TypeScript this is authoritative.
What do the flags g, i, m, s, u and y mean?
g (global) finds every match instead of stopping at the first. i (ignore case) makes the pattern case-insensitive. m (multiline) makes ^ and $ match the start and end of each line rather than the whole string. s (dotAll) lets . match newline characters too. u (unicode) enables full Unicode handling, including \p{…} property escapes and correct surrogate-pair matching. y (sticky) anchors each match to the position right after the previous one (lastIndex). You can combine any of them — this tester shows all matches when g or y is on, and just the first match otherwise, mirroring how RegExp.prototype.exec actually behaves.
How do capture groups and named groups work?
Parentheses ( ) create a numbered capture group; the text each group matched is shown under every result as Group 1, Group 2 and so on, in the order the opening parentheses appear. Use (?<year>\d{4}) to give a group a name — named groups appear in the results by name and can be referenced in a replacement as $<year>. A non-capturing group (?:…) groups part of the pattern for a quantifier or alternation without creating a numbered capture, which keeps your group numbers clean and is slightly faster.
How do I use the replace feature?
Switch on “Show replace” and type a replacement string. Use $1, $2 … to insert numbered capture groups, $<name> for named groups, $& for the whole match, and $$ for a literal dollar sign. For example, pattern (\w+)\s(\w+) with replacement $2 $1 swaps two words. The replacement respects your flags: without the g flag only the first match is replaced. The output is computed with String.prototype.replace, so it matches your code exactly.
Why is my regex showing “Catastrophic backtracking” or running slowly?
Patterns with nested quantifiers on overlapping text — classically (a+)+, (.*)*, or (\w+\s?)+ against a long non-matching string — can make the engine try an exponential number of paths, freezing the tab. This is called catastrophic backtracking and it is a real denial-of-service risk (ReDoS) in production. Fix it by making quantifiers possessive in spirit: anchor the pattern, replace nested groups with a single character class (e.g. [\w\s]+ instead of (\w+\s?)+), or add boundaries so the engine can fail fast. This tester caps iteration to stay responsive, but a pattern that is slow here will be slow in your app too.
Is my pattern or test text uploaded anywhere?
No. Everything runs locally in your browser using the native RegExp engine — your pattern, flags and test string never leave the page and nothing is sent to a server or stored. You can disconnect from the internet and the tester still works. That makes it safe to paste log lines, sample data or anything sensitive while you build a pattern.