String.prototype.match()

Two methods wearing one name: the g flag changes what you get back entirely. Both share the same trap — no match returns null, not an empty array.

String methodES3 (1999)Live demo
Common call
s.match(/\d+/g)
Returns
an array of matches, or NULL
Replaces
a manual exec loop
Watch out
null on no match — guard before .length or .map
string.match(regexpregexp — The pattern. With the g flag you get every whole match and nothing else; without it you get one match with its capture groups, index and input attached.type: RegExp · required)
→ string[] | null

Demo

Live evaluation
Try:
Inputs
sstringtext containing numbers
Output
'a1b22c333'.match(/\d+/g)
['1', '22', '333']

With the g flag the result is a plain array of the whole matches — no index, no capture groups, just the text. The third case is the one that causes real bugs: when nothing matches you get null, NOT an empty array. Calling .length or .map on that throws, so every match call needs a guard or a ?? [] fallback. Without the g flag the shape changes completely: you would get a single match with its capture groups, its index and the original input attached as properties.

Parameters

NameTypeRequiredDescription
regexpRegExpyesThe pattern. With the g flag you get every whole match and nothing else; without it you get one match with its capture groups, index and input attached.

Return value

string[] | null — With /g, an array of the whole matches. Without it, an array-like holding the match plus its capture groups, index and input. NULL when nothing matches, either way.

Common patterns

Always provide a fallback
Turns the null case into an empty array.
const nums = s.match(/\d+/g) ?? [];
Capture groups, without g
Destructure the match and its groups.
const [, year, month] = s.match(/(\d{4})-(\d{2})/) ?? [];
Just testing? Use test
No allocation, and a plain boolean.
if (/\d/.test(s)) { }

Examples

1. Global: whole matches
'a1b2'.match(/\d/g)
Returns
['1', '2']
2. No match is null
'abc'.match(/\d/g)
Returns
null
3. Without g: one match
'a1b2'.match(/\d/)
Returns
['1'] // plus index, input
4. The index property
'abc'.match(/b/).index
Returns
1
5. Capture groups
'a1'.match(/(\w)(\d)/)
Returns
['a1', 'a', '1']
6. Groups lost with g
'a1'.match(/(\w)(\d)/g)
Returns
['a1']

Pitfalls

1. No match returns null, not an empty array
The single most common match bug. Every array method you would reach for next — length, map, filter, forEach — throws on null, and the failure happens only for inputs that contain no match, which are exactly the ones nobody tests.
Throws
'abc'.match(/\d/g).length
TypeError: Cannot read properties of null (reading 'length')
Fallback
('abc'.match(/\d/g) ?? []).length
0
2. The g flag silently changes the return shape
Without g you get capture groups, an index and the input. With g you get only the whole matches and none of that. Adding the flag to fix "it only found one" quietly deletes the groups your destructuring relied on.
Groups gone
'a1'.match(/(\w)(\d)/g)
['a1'] // no groups
Drop g, or use matchAll
'a1'.match(/(\w)(\d)/)
['a1', 'a', '1']
3. You cannot have groups AND every match
That combination is precisely what match cannot do, and it is why matchAll was added in ES2020. Reaching for an exec loop to work around it is the old answer; matchAll is the current one.
Only whole matches
'a1 b2'.match(/(\w)(\d)/g)
['a1', 'b2']
matchAll keeps groups
[...'a1 b2'.matchAll(/(\w)(\d)/g)].map(m => m[1])
['a', 'b']
4. A shared /g regex carries lastIndex state
match with g resets lastIndex, but test and exec on the same regex object do not — so a module-level /g regex used by several functions gives different answers depending on call order. Define the regex where it is used.
Stateful
const RE = /a/g;
RE.test("a");
RE.test("a")
true, then false
No g for testing
const RE = /a/;
RE.test("a");
RE.test("a")
true, then true

When to use

Use it
  • Extracting every occurrence of a pattern, with g
  • Pulling capture groups out of a single match, without g
  • Finding where a pattern matched, via the index property
Reach for something else
  • You only need a yes/no answer → RegExp.test
  • You need groups for every match → matchAll
  • The pattern is literal text → includes or indexOf
  • You want to substitute → replace or replaceAll

Notes

Complexity
Depends entirely on the pattern; a poorly written regex can backtrack exponentially
Return
A new array or null; the string is untouched
CPython impl
V8: Builtins-string-match / regexp.cc
Memory
Allocates the result array and every matched substring
Thread-safe
Single-threaded; a /g regex object carries mutable lastIndex state

FAQ

A legacy decision from ES3 that the language cannot change now. It is genuinely inconsistent — matchAll returns an empty iterator, and filter returns an empty array — so treat every match call as returning a nullable and add ?? [] by reflex.

const found = s.match(re) ?? [];

History

ES3
match added with the null-on-failure behaviour.
ES2015
Symbol.match allowed custom matcher objects.
ES2020
matchAll added, finally allowing groups across every match.