Groups, captures and backreferences

Use parentheses to capture parts of a match, name them for readability, and refer back to text the pattern already matched.

Capturing versus non-capturing

Parentheses do two jobs at once: they group for a quantifier or an alternation, and they record the text they matched. When you only need the grouping, add ?: so the engine does not spend a slot and a copy on a value you will never read.

// grouping only, nothing captured
/^(?:https?|ftp):\/\//.exec('https://example.com')[0];   // 'https://'

// captures are read from the match array
const m = /(\d{4})-(\d{2})-(\d{2})/.exec('2026-09-18');
m[0];        // '2026-09-18'  the whole match
m[1];        // '2026'
m[3];        // '18'
m.index;     // 0, offset of the match in the subject
m.length;    // 4, one entry per group plus the whole match
SyntaxCaptured?Use it for
(abc)yes, numberedextracting a value you need afterwards
(?:abc)nogrouping an alternation or a quantifier
(?<name>abc)yes, named and numberedreadable extraction when several groups exist
(?<=abc)noa lookbehind, an assertion rather than a capture

Named and nested groups

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { year, month, day } = re.exec('2026-09-18').groups;
// year '2026', month '09', day '18'

// nesting is numbered by the position of each opening bracket
const nested = /((\d{4})-(\d{2}))-(\d{2})/.exec('2026-09-18');
nested[1];   // '2026-09'
nested[2];   // '2026'
nested[3];   // '09'
nested[4];   // '18'
  • Numbering follows opening parentheses from left to right, so inserting one group renumbers every capture after it. Named groups remove that fragility.
  • A group that did not participate in the match is undefined, not an empty string. Check before you use it, especially with optional branches.
  • Captures inside a repeated group keep only the last iteration: /(\w)+/.exec('abc')[1] is 'c'. If you need every iteration, match them with matchAll or split afterwards.
  • Python reads the same values as m.group('year') and m.groupdict(); .NET uses m.Groups['year'].

Backreferences

A backreference matches the text a group matched earlier, not the pattern again. (\w)\1 finds a doubled character; (\w)(\w) would find any two characters.

/(\w)\1/.exec('look')[0];            // 'oo'
/(['"]).*?\1/.exec('say "hi"')[0];      // '"hi"'
/(?<q>['"]).*?\k<q>/.exec('he said "stop"')[0];   // '"stop"'

// the trap: inside a repeated group, \1 remembers only the last iteration
/(\w)+\1/.exec('abcc')[0];             // 'abcc', because group 1 ended on 'c'

// what you probably wanted instead
/^(\w)\1/.test('aabb');                // true: a doubled letter at the start
💡
Backreferences make a pattern non-regular, so engines can no longer compile it to a fast automaton and RE2 rejects it outright. When the only goal is matching balanced quotes, writing both alternatives explicitly is often simpler and always faster.

FAQ

Should I always use named groups?
Use them whenever you read a value by name in the surrounding code. They cost a little verbosity and remove the whole class of bugs caused by a group being inserted or reordered. Anonymous groups are fine for throwaway tests.
Why is my capture group undefined?
The branch that contains it did not participate in the match, usually because an | alternative was taken or an optional part was skipped. Test with m.groups.name !== undefined before using the value.

Character classes, quantifiers and anchors Lookahead and lookbehind

Last refreshed 2026-09-18.