Quick answer
Use test() when you only need a yes/no answer. Use match() when you want one match or all matches as an array. Use matchAll() when you want every match with its capture groups. Use exec() when you want to iterate matches statefully (rare, but useful for streaming over very long inputs).
JavaScript has four regex methods that look interchangeable in tutorials but behave very differently in production. The choice changes correctness, performance, and whether your global flag corrupts state across calls. Here is the practical mapping.
// All four methods on the same input
const re = /(\d+)/g;
const text = 'Build 42 shipped on day 4 of week 18.';
// 1. test() returns a boolean
re.test(text); // true (and advances lastIndex!)
// 2. match() returns array of matches (no groups when /g)
text.match(/(\d+)/g); // ['42', '4', '18']
// 3. matchAll() returns iterator with full groups
[...text.matchAll(/(\d+)/g)]; // [['42', '42', ...], ...]
// 4. exec() returns one match per call, advances lastIndex
let m, hits = [];
const re2 = /(\d+)/g;
while ((m = re2.exec(text)) !== null) hits.push(m[0]);
// hits = ['42', '4', '18']
test(): the boolean check
RegExp.prototype.test(str) returns true if the pattern matches anywhere in the string, false otherwise. It is the right choice when you only care about presence, not the matched text.
The trap: when the regex has the global flag, test() mutates the regex object. Specifically, it sets lastIndex to the position after the last match. Calling test() twice in a row on the same regex with /g can return different results.
Workaround: do not give test() the g flag unless you specifically want stateful behavior. For a yes/no check, the literal /foo/ is correct and side-effect-free.
match(): the simple extraction
String.prototype.match(regex) behavior depends on the global flag:
- Without
/g: returns the first match as an array, with capture groups at indices 1+. Returnsnullif no match. - With
/g: returns an array of every match string, but discards capture groups entirely.
This asymmetry is the most common source of "where did my groups go?" bugs. If you want all matches with their groups intact, use matchAll(), not match() with /g.
matchAll(): the right answer for "all matches with groups"
String.prototype.matchAll(regex) requires a global regex (/g flag is mandatory) and returns an iterator. Each iteration yields a full match array with groups, exactly like match() without /g.
Spread it into an array if you want random access: [...str.matchAll(re)]. Iterate it directly for streaming over a long input. The iterator is lazy, so you can break early without paying for the rest.
Available since Chrome 73, Firefox 67, Safari 13. If you support anything older, the exec() loop below is the fallback.
exec(): the stateful iterator
RegExp.prototype.exec(str) returns one match per call when used with the global flag. lastIndex on the regex object tracks where the next call will resume. Calling in a loop until null walks every match, just like matchAll().
The reason exec() still exists: it is the only way to interleave processing between matches. If you need to look at match N before deciding whether to look at match N+1 (early-exit, transformation, branching) exec() in a loop is more natural than turning matchAll() into a generator and consuming it manually.
Without the global flag, exec() returns the first match and ignores lastIndex. Calling it repeatedly returns the same first match forever.
Performance: the differences are real but small
For most real workloads (under 10,000 matches per call) the difference between methods is rounding error compared to the regex engine itself. The numbers, on V8, on a typical pattern:
test(): fastest for "match or not" (no allocation).matchAll(): small overhead for the iterator, comparable toexec()in a loop.match()with/g: same speed asmatchAll()but you lose group data.match()without/g: same asexec()on a non-global pattern.
The real performance question is whether the regex itself is well-formed. A bad pattern (catastrophic backtracking) is millions of times slower than any method choice.
Decision flowchart
Walk through it the next time you need a regex method:
- Do you only need yes/no? Use
test(). Drop the global flag. - Do you need just the first match? Use
match()without/g. - Do you need all matches without groups? Use
match()with/g. - Do you need all matches with groups? Use
matchAll()with/g. - Do you need to interleave logic between matches? Use
exec()with/gin a loop.
That covers 99% of cases. If you find yourself reaching for String.prototype.split() or replace() with a regex, those are valid too, just for different goals (tokenizing, transforming).