RegExp.prototype.exec()

Searches a string and returns an array of the match and its capture groups, or null.

Signature
exec(str)
  • Added in JavaScript ES3 (1999)

Parameters

ParameterTypeDescription
$strstringThe string to search. Any value is coerced to a string; omitting it searches for the literal text "undefined".

Return value

Array | nullOn success, an array whose element 0 is the full match and whose remaining elements are the capture groups, plus index, input, groups and (with the d flag) indices properties. On failure, null, and lastIndex is set to 0.

Changelog

  • PHP ES2018The groups property was added, holding named capture groups on a null-prototype object (undefined when the pattern defines none).
  • PHP ES2022The indices property was added, present only when the regex carries the d flag.

exec() is the primitive every other regex operation is built on. It gives you more than test(): the matched text at index 0, each capture group after it, the offset in index, the searched string in input, and named groups on groups.

Named groups beat numbered ones

Write (?<year>\d{4}) instead of (\d{4}) and read m.groups.year. Adding a group in the middle of the pattern no longer renumbers everything downstream. groups has a null prototype, so a group named toString is safe, but object helpers like hasOwnProperty are not inherited on it.

Looping

With the g or y flag, each call resumes from lastIndex and returns the next match, then null once the string is exhausted — which is what makes the while loop work. Without those flags lastIndex is ignored, every call returns the same first match, and the loop never ends. For plain iteration, String.prototype.matchAll() is safer: it requires g and keeps no shared state.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Pull the year, month and day out of a date string by name
  • Walk every match in a document while tracking each one's offset
  • Read the exact start and end offsets of a capture group with the d flag

Frequently asked questions

Why does my exec() while loop hang forever?

The regex is missing the g (or y) flag, so lastIndex is never advanced and exec() keeps returning the same first match. Add g to the pattern, or switch to String.prototype.matchAll(), which throws if the flag is missing instead of looping.

How do I get named capture groups from exec()?

Name each group with (?<name>...) in the pattern and read result.groups.name. groups is undefined when the pattern has no named groups, so use result.groups?.name if the pattern is supplied by a caller.

Should I use exec() or matchAll()?

Use matchAll() for straightforward iteration — it needs no shared lastIndex and cannot loop forever. Use exec() when you need to control where the search resumes, such as a sticky tokenizer that rewinds or skips ahead.

More examples

Iterating every match with the g flag

Each call resumes at lastIndex and returns the next match; the final call returns null and resets lastIndex to 0, ending the loop. matchAll produces the same matches but leaves no state behind on the regex.

Example · javascript
const token = /\w+/g;
const source = "a bb ccc";
let m;

while ((m = token.exec(source)) !== null) {
  console.log(m[0], "at", m.index, "-> lastIndex", token.lastIndex);
}

// matchAll does the same without the shared state:
console.log([...source.matchAll(/\w+/g)].map(x => x[0]));

Exact offsets with the d flag

The d flag adds an indices array of [start, end] pairs, one per capture group, plus indices.groups keyed by name. Without d the property is simply absent, so feature-detect it rather than assuming.

Example · javascript
const re = /(?<word>\w+)/d;
const m = re.exec("  hey");

console.log(m[0]);                  // "hey"
console.log(m.indices[0]);          // [ 2, 5 ]
console.log(m.indices.groups.word); // [ 2, 5 ]

console.log(/(\w+)/.exec("  hey").indices); // undefined

Discussion

  • Be the first to comment on this lesson.