String.prototype.matchAll()

The method that closed the gap: match with /g gives every match but drops the groups, and without /g keeps the groups but finds only one. This gives you both.

String methodES2020Live demo
Common call
[...s.matchAll(re)]
Returns
an iterator of match objects — spread it
Replaces
a while loop over regex.exec
Watch out
the regex MUST have the g flag or it throws
string.matchAll(regexpregexp — The pattern, which must carry the g flag — a non-global regex is a TypeError, exactly as with replaceAll.type: RegExp · required)
→ Iterator

Demo

Live evaluation
Try:
Inputs
sstringtext containing numbers
Output
[...'a1b22c333'.matchAll(/\d+/g)].map(m => m[0])
['1', '22', '333']

The demo spreads the iterator and takes m[0], the whole match, so the output is comparable with the match page — but each m is a full match object, so m[1] would be the first capture group and m.index its position. Compare the third case directly with match: where match returns NULL and throws on .length, matchAll returns an empty iterator that spreads to an empty array. That alone removes the most common regex bug in JavaScript.

Parameters

NameTypeRequiredDescription
regexpRegExpyesThe pattern, which must carry the g flag — a non-global regex is a TypeError, exactly as with replaceAll.

Return value

Iterator — An ITERATOR of full match objects, each with capture groups, index and input. Not an array — spread it or loop it. Empty rather than null when nothing matches.

Common patterns

Every match with its groups
The reason the method exists.
const pairs = [...s.matchAll(/(\w)=(\d)/g)].map(m => [m[1], m[2]]);
Named groups read better
m.groups is an object keyed by group name.
for (const m of s.matchAll(/(?<key>\w+)=(?<value>\d+)/g)) {
  console.log(m.groups.key, m.groups.value);
}
Positions of every match
index is on each match object.
const positions = [...s.matchAll(re)].map(m => m.index);

Examples

1. Whole matches
[...'a1b2'.matchAll(/\d/g)].map(m => m[0])
Returns
['1', '2']
2. No match is empty
[...'abc'.matchAll(/\d/g)]
Returns
[]
3. match gives null
'abc'.match(/\d/g)
Returns
null
4. Groups preserved
[...'a1'.matchAll(/(\w)(\d)/g)].map(m => m[1])
Returns
['a']
5. match loses them
'a1'.match(/(\w)(\d)/g)
Returns
['a1']
6. Non-global throws
[...'a1'.matchAll(/\d/)]
Returns
TypeError: String.prototype.matchAll called with a non-global RegExp argument

Pitfalls

1. It returns an iterator, not an array
Array methods are not available on the result — .length, .map and .filter are all undefined. Spread it or use Array.from first, or loop it directly with for...of.
Not an array
'a1'.matchAll(/\d/g).length
undefined
Spread it
[...'a1'.matchAll(/\d/g)].length
1
2. The iterator is single-use
Once consumed it is exhausted, so spreading it twice gives an array and then an empty one. If you need the matches more than once, materialise them into an array and reuse that.
Second is empty
const it = s.matchAll(re);
[...it].length;
[...it].length
n, then 0
Store the array
const all = [...s.matchAll(re)];
reusable
3. A non-global regex throws
The same rule as replaceAll, for the same reason — the name promises every match, so a pattern that could only find one is rejected rather than silently misbehaving.
Throws
[...'a1'.matchAll(/\d/)]
TypeError: String.prototype.matchAll called with a non-global RegExp argument
Add the flag
[...'a1'.matchAll(/\d/g)]
one match object
4. A zero-length match needs care in hand-rolled loops
matchAll advances lastIndex itself, so a pattern that can match an empty string is handled safely. The exec loop it replaces does not — forgetting to advance manually there spins forever, which is the other reason to stop writing them.
Infinite exec loop
while ((m = /a*/g.exec(s)) !== null) { }
hangs
matchAll is safe
for (const m of s.matchAll(/a*/g)) { }
terminates

When to use

Use it
  • Every match together with its capture groups
  • Named groups across multiple matches
  • The index of each match in a single pass
  • Anywhere a while-exec loop would otherwise appear
Reach for something else
  • You only want the whole matches → match with g is shorter
  • You only need a boolean → RegExp.test
  • The pattern is literal text → includes, indexOf or split
  • Targeting runtimes older than 2020 → an exec loop, carefully

Notes

Complexity
Depends on the pattern; lazy — matches are found as you iterate
Return
An iterator of match arrays; the string is untouched
CPython impl
V8: Builtins-string-matchall / regexp.cc
Memory
Lazy, so the whole result set need not be held at once unless you spread it
Thread-safe
Single-threaded; matchAll clones the regex internally, so lastIndex on yours is not disturbed

FAQ

So it can be lazy. A pattern over a very large string need not build every match up front, and for...of can stop early. Spreading it into an array when you want one is a single extra character of syntax.

for (const m of s.matchAll(re)) { if (done) break; }

History

ES2020
matchAll added, resolving the long-standing groups-versus-global tradeoff in match.