The g flag: global matching and the lastIndex trap
The g flag makes a pattern find every match instead of stopping at the first, and it gives the RegExp object a lastIndex cursor that survives between calls.
At a glance
- Flag letter
- g
- Property
- RegExp.prototype.global
- Makes stateful
- exec and test, which resume from lastIndex on the same object
- Required by
- String.prototype.matchAll and replaceAll, both throw a TypeError without it
- Empty matches
- advance one character, so a zero width pattern cannot loop forever
- In this tester
- the tests tab strips g before evaluating a case, so lastIndex cannot skew a result
In detail
Without g, replace changes the first match and leaves the rest alone, and exec returns that same first match every time you call it. With g, replace visits every match, split cuts at every match, and matchAll can walk the whole string. The flag never changes what the pattern matches, only how many times the search is allowed to run.
The part that catches people is that a RegExp is an object with mutable state. When g is set, exec and test write the end offset of the match into lastIndex and start the next search from there. Call test twice on the same literal object with the same string and you get true, then false, because the second call began after the match the first one found.
That is why a global pattern stored in a module level constant is a bug waiting for a list. A filter written as list.filter((s) => RE.test(s)) drops roughly half its entries, and because a failed call resets lastIndex to zero the results alternate rather than failing outright. Build the regex inside the function, reset lastIndex by hand, or keep a separate non global copy for tests.
Two string methods refuse to guess. matchAll and replaceAll both throw a TypeError when handed a pattern without g, on the grounds that you probably meant to iterate. replace has no such guard, so a missing g there is silent: one occurrence changes and the rest of the string goes out untouched.
Examples
const re = /\d+/g;
re.test("a1"); // true, re.lastIndex is now 2
re.test("a1"); // false, the search started at index 2
re.test("a1"); // true, the failed call reset lastIndex to 0Nothing about the input changed. Create the RegExp inside the function, or drop g when you only need a yes or no answer.
"banana".replace(/a/, "o"); // "bonana"
"banana".replace(/a/g, "o"); // "bonono"
[..."a1 b2".matchAll(/\w(\d)/g)].map((m) => m[0]); // ["a1", "b2"]
"a1 b2".matchAll(/\w(\d)/); // TypeError, matchAll needs a global regexThings that catch people out
- A global RegExp kept in a module constant carries lastIndex between calls. list.filter((s) => RE.test(s)) then drops about half the list, and the entries it drops depend on the length of the previous one.
- A failed test or exec resets lastIndex to 0, so the bug alternates instead of failing consistently. That is exactly why it survives a quick manual check and shows up in production.
- String.prototype.replace does not complain about a missing g. It replaces the first match and returns a string that looks almost right.
- An inline literal such as /x/g creates a fresh object on every evaluation, so the same pattern behaves differently depending on whether it was hoisted into a variable.
Try it: Regex Tester
Type a pattern and a test string, watch every match highlight as you go, read a plain English breakdown of each token, and check the numbered and named groups it captured.
Regex Tester
LOCAL · Browser-OnlyTest and debug regular expressions with live matching and highlights
Related Regex syntax
All regex syntax →The y flag: sticky matching at lastIndex
How the sticky flag anchors a match to the current lastIndex, why it is the right tool for a hand written tokenizer, and how it behaves alongside g.
The m flag: multiline anchors
What multiline mode changes about ^ and $, which line terminators JavaScript counts, and why the m flag never makes the dot match a newline.
The i flag: case insensitive matching
How the i flag folds case in literals and ranges, what it deliberately leaves alone, and the two non-ASCII characters it starts matching once u is on.
The s flag: dotAll mode
What dotAll changes, why it has no effect at all on character classes, and the [\s\S] idiom that did the same job before the s flag existed.
The u flag: Unicode mode
What the u flag changes about how a pattern is parsed, the escape forms it unlocks, and the previously tolerated syntax it turns into a hard error.
The d flag: capture group start and end offsets
What the d flag adds to a match result, how indices.groups mirrors named captures, and when the extra offsets are worth asking the engine for.
Tools for this
- Regex Tester Free online regex tester - test and debug regular expressions with live matching and highlights
- Regex Playground Regex playground with live match highlighting, named groups, find and replace, and shareable links