How to Test a Regular Expression in JavaScript
Test a JavaScript regular expression, read its flags and capture groups, use $1 and $<name> in replacements, and avoid empty-match and backtracking surprises.
To test a regular expression in JavaScript, call regex.test(text) for a yes or no, or use text.matchAll(regex) to get every match with its position and capture groups. To see the same thing highlighted without writing code, paste the pattern and some sample text into the Regex Tester.
Three ways to test a regex in code
test() tells you whether there is a match, match() returns the first match with its groups, and matchAll() walks through every match:
const date = /(\d{4})-(\d{2})-(\d{2})/;
date.test("Released on 2026-10-01");
// true
"Released on 2026-10-01".match(date);
// ["2026-10-01", "2026", "10", "01"], with index 12
[..."a1b22".matchAll(/\d+/g)].map((m) => [m[0], m.index]);
// [["1", 1], ["22", 3]]matchAll() needs the g flag and throws a TypeError without it.
One trap: a regex with the g or y flag remembers where its last match ended in lastIndex, so the next call starts from there.
const re = /a/g;
re.test("a"); // true
re.test("a"); // false, the search resumed after the first matchWhen you only need a yes or no answer, leave out g, or set re.lastIndex = 0 before each call.
What do the regex flags do?
Flags go after the closing slash, as in /hello/gi, and in any order. The Regex Tester has a switch for six of them. Each example below is a pattern and text you can paste in to see the difference:
| Flag | What it does | Try it |
|---|---|---|
| g | Find every match, not just the first | \d+ on a1 b22 c333 finds 1 match without g and 3 with it. |
| i | Ignore case | hello on Hello hello HELLO first matches hello; with i the first match is Hello. |
| m | ^ and $ match at every line break, not only at the ends of the text | ^\w+ with g on three lines (one, two, three) finds only one; with gm it finds all three. |
| s | The dot also matches line breaks | a.b on a, a line break, then b has no match without s and matches with it. |
| u | Read the text as Unicode code points and allow \p{...} classes | \p{L}+ with g on héllo wörld 123 finds both words only with u, and nothing without it. And . with g on a😀 gives 3 matches without u (the emoji is split in two) and 2 with it. |
| y | Sticky: a match must start exactly where the search position is | \d+ finds 123 in 123 456 but nothing in x123. With gy the tester lists 123 and stops at the space. |
JavaScript has two more flags that the tester has no switch for: d, which adds the start and end of each group to the result, and v, an upgraded version of u.
How do capture groups work?
Parentheses capture the part of the match they enclose. Groups are numbered by their opening parenthesis, from left to right, and (?<name>...) gives a group a name. A named group is numbered as well, so it can be read either way:
const m = "2026-10-01".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
m.groups.year; // "2026"
m[1]; // "2026"The Regex Tester opens with this idea: the pattern (?<year>\d{4})-(\d{2})-(\d{2}) on Released on 2026-10-01, patched on 2026-10-14. finds two matches with the g flag, which the tester starts with. Under Match details, the first one lists $1: 2026, $2: 10, $3: 01 and year: 2026, because year is also group 1.
(?:...)groups without capturing, so it takes no number:(?:a)(b)onabhas one group,b.- A group that did not take part in the match, such as
(b)?in(a)(b)?conac, isundefined. The tester shows it asundefined. \k<name>matches the same text a named group matched:(?<title>\w+), yes \k<title>matchesSir, yes Sir.
How do $1, $<name> and $& work in a replacement?
In the replacement text of replace(), a dollar sign followed by a code inserts part of the match. The tester's Replace with field uses the same rules, and its default $3/$2/$<year> turns 2026-10-01 into 01/10/2026.
| Code | Inserts | Example |
|---|---|---|
| $1, $2, ... | A numbered group | (\w+) (\w+) on John Smith with $2, $1 gives Smith, John |
| $<name> | A named group | (?<first>\w+) (?<last>\w+) with $<last> $<first> gives Smith John |
| $& | The whole match | (\w+) (\w+) on John Smith with $&! gives John Smith! |
| $` and $' | The text before and after the match | \d+ on a1b with [$`|$&|$'] gives a[a|1|b]b |
| $$ | A literal dollar sign | \d+ on price 5 with $$$& gives price $5 |
A few details that catch people out:
- Without the
gflag only the first match is replaced.\d+ona1 b22 c333with#givesa# b22 c333; withgit givesa# b# c#. - A code that points at a group that does not exist stays as written:
$3in a pattern with two groups gives$3, and$<first>in a pattern with no named groups stays$<first>. - A group that exists but did not match becomes an empty string.
(a)|(b)onabwith[$1][$2]and thegflag gives[a][][][b]; withoutgonly the first match is replaced, which gives[a][]b. - With fewer than ten groups,
$10means group 1 followed by a0.(\w+)onJohnwith$10givesJohn0.
Why does a* give empty matches?
The * means zero or more, so a* can match an empty string anywhere there is no a. On baaac with the g flag it finds four matches: an empty one at position 0, aaa at 1 to 4, an empty one at 4 (before the c) and an empty one at the end, 5. Replacing with X, still with g, gives XbXXcX; without g only the empty match at 0 is replaced, which gives Xbaaac.
An empty match does not move the search forward, so the engine has to step past it or it would find the same empty match forever. It advances by one UTF-16 code unit, or by one whole character when the u flag is on. You can see the difference on an emoji: a* with g on 😀 has three empty matches (at 0, 1 and 2, the middle one inside the character) and two with gu (at 0 and 2).
The tester steps past empty matches the same way. It highlights only matches that have text, but the count includes the empty ones, and Match details lists each of them as empty. If you meant "at least one a", write a+.
How do I escape special characters?
These characters have a meaning in a pattern: \ ^ $ . * + ? ( ) [ ] { } |. Put a backslash in front of one to match it literally. In a regex literal the slash that ends the pattern needs one too, as in /a\/b/; in the tester you type the pattern without slashes, so a / needs none.
Without escaping, 1+1=2? means "one or more 1s, another 1, an equals sign, and an optional 2": it matches 11=2 and does not match 1+1=2. Escaped, 1\+1=2\? matches the text you meant.
To build a pattern from text a user typed, escape the text first:
const escapeRegExp = (text) => text.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
new RegExp(escapeRegExp("1+1=2?")).test("is 1+1=2? yes"); // trueNewer engines have RegExp.escape(text) for this. MDN lists it as Baseline 2025, meaning it has worked across the latest browsers since May 2025, so check your targets before relying on it. It also escapes a leading letter or digit, so RegExp.escape("a.b") returns \x61\.b, which still matches a.b.
Two more places where backslashes bite:
- Strings need their own backslashes.
new RegExp("\\d+")is the pattern\d+, butnew RegExp("\d+")isd+, because\din a string is justd. The same doubling applies to a pattern stored in a JSON string; the JSON guide covers JSON escaping. - The u flag is stricter. Escaping a character that is not special, such as
\aor a\-outside a character class, is an error withu; the tester shows Invalid pattern: Invalid escape. Withoututhey are allowed.
Escaping for a regex is a different job from percent-encoding for a URL; see how to URL encode a string for that one.
What is catastrophic backtracking?
A regex engine like the one in your browser tries one way to match, and when that fails it goes back and tries the other ways. Some patterns have a huge number of ways. The classic example is (a+)+$: a run of a characters can be divided between the inner and the outer + in exponentially many ways. If the text ends in something that makes $ fail, such as a b, the engine tries them all before it gives up.
/(a+)+$/.test("a".repeat(24) + "b");Timed in Node 25 (V8) on a laptop, this takes about 50 ms with 22 a characters, 200 ms with 24 and 800 ms with 26. Each extra a roughly doubles the time. Your numbers will differ, but the doubling is the point: a few more characters turn milliseconds into minutes.
The Regex Tester runs your pattern on the page itself, so a pattern like this on longer text can make the tab wait until the engine finishes. The limits of 100,000 characters of text and 1,000 listed matches do not prevent that.
To avoid it, do not put a quantifier on a group whose contents can already match the same text in several ways. Write a+$ instead of (a+)+$. That removes the exponential blow-up, but a long run of a characters followed by a b still costs time that grows with the square of the length: about 175 ms at 20,000 characters and 700 ms at 40,000 in the same test. So keep the test text short while you work on a pattern, and add the long input last.
Try the Regex Tester
The Regex Tester runs everything above in your browser:
- Type the pattern in Pattern and switch the
g i m s u yflags on and off. - Paste text into Test text. Matches are highlighted as you type, and the heading shows the count.
- Match details lists each match with its position and its numbered and named groups.
- Type a replacement in Replace with and copy the Result.
- A pattern that does not compile shows the engine's reason, such as Invalid pattern: Unterminated group for
(. - Past 1,000 matches the heading says it is showing the first 1,000. Text over 100,000 characters is not matched, and a message says so.
How to test a regular expression in JavaScript
1. Write the pattern and choose flags
Type the pattern without the surrounding slashes and switch on the flags you need: g for every match, i to ignore case, and m, s, u or y when the text calls for them.
2. Paste text that should and should not match
Use sample text with matching lines, near misses and empty lines, so you see what the pattern accepts as well as what it rejects.
3. Read the matches and capture groups
Check the highlighted matches, then the match details: each match's position, its numbered groups and its named groups.
4. Preview the replacement
Type a replacement such as $3/$2/$<year> and check the result. Without the g flag only the first match is replaced.
5. Carry the pattern into your code
Use the same pattern and flags in a regex literal or new RegExp(pattern, flags). Double the backslashes when the pattern is in a string, and do not reuse a regex with the g flag across test() calls.
// Frequently Asked Questions
How do I test a regex in JavaScript without writing code?
Paste the pattern and some sample text into the Regex Tester. It runs your browser's own RegExp engine, so the result matches what the same pattern does in your code. Every match is highlighted and listed with its position and capture groups, and a replace field previews the result.
Why does my regex work in the tester but not in Python or PHP?
Regex flavors differ. The tester uses the JavaScript flavor only, so a pattern that relies on another flavor's syntax, such as possessive quantifiers, is not valid there. Check the pattern against the engine that will run it.
Why does regex.test() return true and then false for the same text?
A regex with the g or y flag remembers where its last match ended in its lastIndex property, and the next call starts from there. Calling test() twice on the same text can therefore give true and then false. Drop the g flag when you only need a yes or no answer, or set lastIndex back to 0 before each call.
How much text can the Regex Tester handle?
The test text is limited to 100,000 characters, and at most 1,000 matches are listed. Past 1,000 the heading says it is showing the first 1,000. These limits do not stop a slow pattern: catastrophic backtracking can still make the page wait.
Is my text sent to a server?
No. Matching and replacing happen in your browser, and nothing you type is uploaded.