Common JavaScript regex syntax
| Token | Matches | Note |
|---|---|---|
. | Any character except newline | Add the s flag to include newlines |
\d \w \s | Digit, word character, whitespace | Uppercase negates: \D \W \S |
[abc] | Any one of a, b or c | [^abc] matches anything else |
* + ? | Zero or more, one or more, optional | Greedy by default |
{2,5} | Between two and five times | {3} is exactly three |
^ $ | Start and end of string | With the m flag, start and end of each line |
\b | Word boundary | Prevents cat from matching inside concatenate |
(…) | Capture group | (?:…) groups without capturing |
(?<name>…) | Named capture group | Read it back as match.groups.name |
(?=…) (?!…) | Lookahead, positive and negative | Asserts without consuming characters |
Greedy and lazy quantifiers
Quantifiers are greedy by default: they consume as much text as possible and backtrack only as needed. Against <b>bold</b> and <i>italic</i>, <.+> matches the entire string because .+ consumes through the last >.
Adding ? makes the quantifier lazy. <.+?> stops at the next > and matches each tag separately. Compare the three forms below:
<.+> → <b>bold</b> and <i>italic</i> (one match, the lot)
<.+?> → <b> </b> <i> </i> (four matches)
<[^>]+> → <b> </b> <i> </i> (four matches, and faster)
The negated character class in the third form cannot cross a >, so it avoids the extra backtracking required by a dot quantifier.
Catastrophic backtracking and ReDoS
Nested quantifiers over overlapping character sets can force a backtracking engine to explore an exponential number of paths before it reports failure. A standard example is (a+)+$ applied to many a characters followed by !.
// This simple pattern can freeze the thread.
/(a+)+$/.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!');
// The same risk appears in nested quantifiers
// whose inner character sets overlap.
/^(\s*\w+)+$/.test(' lots of words here x');
This failure mode is called regular expression denial of service (ReDoS). It has caused production outages, including Cloudflare’s 2019 outage. Treat any pattern that processes user-controlled input as an application security boundary.
- Avoid nesting quantifiers, especially
(x+)+,(x*)*and(x|y)*wherexandycan match the same text. - Prefer a constrained character class such as
[^>]+to a lazy dot such as.+?when the delimiter is known. - Anchor patterns with
^and$so failure is detected early instead of retried at every offset. - On a server, limit input length before matching or use a non-backtracking engine such as Go’s RE2 or Rust’s
regexcrate.
Cases that need a parser instead
Do not parse HTML or XML with a regular expression. These formats can nest elements and contain markup inside comments or attributes. Use an HTML, XML or DOM parser that understands the document structure.
Do not use a strict regex as proof that an email address works. RFC 5322 permits quoted local parts, comments and IP-literal domains. A basic shape check can catch obvious input errors, but a confirmation message is needed to establish that the address can receive mail.
Capture groups in practice
const LOG = /^(?<ts>\S+) \[(?<level>\w+)\] (?<msg>.+)$/;
const { groups } = LOG.exec('2026-08-16T09:14:02Z [ERROR] payment declined');
groups.level; // level is 'ERROR'
groups.msg; // msg is 'payment declined'
// Iterate through matches with positions and groups
for (const m of text.matchAll(/(\w+)=(\w+)/g)) {
console.log(m[1], m[2], m.index);
}
// Use groups in replacement text
'2026-08-16'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1'); // Produces '16/08/2026'
Named groups make consumers less dependent on group order. groups.level still identifies the intended value if another capture group is inserted earlier in the pattern; match[2] may not.
Frequently asked questions
Why does my global regex skip every other match?
A regex with the g flag keeps a mutable lastIndex between calls. Reusing the same object with test() or exec() resumes from that position. Create a new regex for each run or reset lastIndex to 0 before reuse.
How do I match across multiple lines?
The s flag makes . match newline characters. The m flag changes ^ and $ so they match line boundaries instead of only the boundaries of the full string. Use either or both according to the pattern.
Is my test data sent anywhere?
No. The browser compiles the pattern and runs it against the text. This tool does not send the pattern or test string to a server.
Why is a pattern from Stack Overflow failing here?
The pattern may target another regex engine. Older Safari versions do not support lookbehind (?<=…). JavaScript does not support possessive quantifiers such as a++, atomic groups such as (?>…), \Z, \A or recursive patterns. This tool uses the browser’s JavaScript engine.
Does the tester protect me from a runaway pattern?
Only partly. The tool stops collecting after 5,000 matches and advances past zero-length matches to avoid an endless loop. It cannot interrupt catastrophic backtracking inside one exec() call. A pathological pattern can still make the browser tab unresponsive.