Test Regex JavaScript: Patterns in the Browser and Node
JavaScript regex is straightforward until the global flag gets involved. Then a regex object starts carrying state between calls, and code that looks correct returns alternating true and false. The regex tester on this site runs the same engine as your browser and Node.js, so a pattern that matches there matches in your code; the traps are in the API around the pattern.
The lastIndex trap
A regex with g or y remembers where it stopped. Reusing the same object across calls resumes from there:
const re = /\d+/g;
re.test('123'); // true
re.test('123'); // false ← lastIndex is now 3
re.test('123'); // true ← reset after failure
// fixes: drop the g flag, or create the regex per call
const check = (s) => /\d+/.test(s);
If a validation function fails every second call with the same input, look for a g flag on a regex stored in a constant or module variable. lastIndex is why.
match, matchAll and exec
str.match(re) without g returns a match object with capture groups; with g it returns an array of full matches only, discarding the groups. That silent change catches people out.
str.matchAll(re) requires g and yields a full match object for every match, with its groups and index, which is usually what you want:
for (const m of '2026-08 2027-01'.matchAll(/(?<y>\d{4})-(?<mo>\d{2})/g)) {
console.log(m.groups.y, m.groups.mo, m.index);
}
Modern flags
s (dotall) since ES2018 lets . cross newlines. u enables proper Unicode handling, and v (ES2024) adds set operations inside character classes.
d (ES2022) adds indices to match results, giving the start and end of each group — useful for highlighting.
Without u, . and quantifiers operate on UTF-16 code units, so an emoji counts as two characters and a character class can split it. Add u whenever the input may contain non-BMP characters.
Replacement
replaceAll needs a g flag if given a regex, and throws otherwise. In replacement strings, $1 and $<name> insert groups, and $$ is a literal dollar.
A function replacement is clearer than a dense pattern when the transformation has conditions — it receives the match, the groups and the offset.
Frequently asked questions
Why does test() alternate between true and false?
The g flag makes the regex stateful via lastIndex. Remove g, or create a new regex per call.
Why did my capture groups disappear?
String.match with the g flag returns only full matches. Use matchAll or exec in a loop.
When do I need the u flag?
Whenever the input can contain emoji or other non-BMP characters, so the engine works on code points rather than UTF-16 units.