iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Groups & Capture

Groups capture and structure matches. (...) captures; (?:...) is non-capturing; (?<name>...) names a group. Backreferences (\1) reuse a captured value.

Capture, non-capture, named, backref

EXAMPLE
// 1) Capture groups
const m = 'order id=1234'.match(/id=(\d+)/);
m[0]    // 'id=1234'   — full match
m[1]    // '1234'      — first capture group

// 2) Multiple groups
const m2 = '2026-06-08'.match(/(\d{4})-(\d{2})-(\d{2})/);
m2[1]    // '2026' (year)
m2[2]    // '06'   (month)
m2[3]    // '08'   (day)

// 3) Non-capturing — for grouping without storage
const m3 = 'hello world'.match(/(?:hello) (\w+)/);
m3[1]    // 'world'
// (?:...) doesn't consume an index in the result

// 4) Named groups (ES2018+)
const m4 = '2026-06-08'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
m4.groups.year    // '2026'
m4.groups.month   // '06'
m4.groups.day     // '08'

// Destructure
const { year, month, day } = m4.groups;

// 5) Alternation
const m5 = 'orange'.match(/(apple|orange|banana)/);
m5[1]    // 'orange'

const m6 = '+61 412 345 678'.match(/^(\+?\d{1,3})\s?(\d{3})\s?(\d{3})\s?(\d{3})$/);
m6     // [full, '+61', '412', '345', '678']

// 6) Optional + repeating groups
'rgb(0, 128, 255)'.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
// → ['rgb(0, 128, 255)', '0', '128', '255']

// 7) Backreferences — refer to a previously-captured group
/(.)\1/.test('hello')              // true — 'll' matches (.)(\1)
/^(['"])(.*?)\1$/.exec(`'single quoted'`)
// → matches the same quote char at start and end
// → ["'single quoted'", "'", 'single quoted']

// 8) Find duplicated words
const dupes = 'the the quick brown fox the'.match(/\b(\w+)\s+\1\b/);
dupes[1]    // 'the'

// 9) Named backreference
/(?<quote>['"])(?<text>.*?)\k<quote>/.exec(`"hi"`);
// → ['"hi"', '"', 'hi']

// 10) Replace with captured groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');     // 'Smith, John'

// Named groups in replace
'2026-06-08'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '$<day>/$<month>/$<year>',
);
// → '08/06/2026'

// 11) Replace with a function
'Hello, World'.replace(
    /(\w+)/g,
    (match, word) => word.toUpperCase(),
);
// → 'HELLO, WORLD'

// Named groups available via 4th+ arg (after groups + offset + string)
'2026-06-08'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    (_, year, month, day) => `${day}/${month}/${year}`,
);

// 12) matchAll — every match with capture groups
const text = 'id=1, id=2, id=3';
for (const m of text.matchAll(/id=(\d+)/g)) {
    console.log(m[1]);        // '1', '2', '3'
}
// matchAll requires the g flag

// 13) Real recipes

// 13a) Parse query string
for (const [, key, value] of 'a=1&b=2&c=hello'.matchAll(/([^=&]+)=([^&]*)/g)) {
    console.log(key, decodeURIComponent(value));
}

// 13b) Extract code blocks from markdown
const markdown = '```python\\nprint(1)\\n```';
for (const m of markdown.matchAll(/```(\w+)\\n([\\s\\S]*?)```/g)) {
    const language = m[1];
    const code = m[2];
}

// 13c) Parse a URL
const url = 'https://user:pass@example.com:8080/path?q=1#frag';
const urlRegex = /^(?<scheme>[a-z]+):\/\/(?:(?<user>[^:]+):(?<pass>[^@@]+)@@)?(?<host>[^:\/?#]+)(?::(?<port>\d+))?(?<path>[^?#]*)(?:\?(?<query>[^#]*))?(?:#(?<frag>.*))?$/;
const { scheme, host, port, path } = url.match(urlRegex).groups;
// (Use new URL() in real code; this is for the example.)

// 13d) Extract HTML tag attributes
for (const m of '<a href="/x" title="link">x</a>'.matchAll(/(\w+)="([^"]*)"/g)) {
    console.log(m[1], m[2]);   // 'href', '/x'  ;  'title', 'link'
}

// 14) Performance
// - Use non-capturing (?:...) when you don't need the value — saves work
// - Avoid deeply nested groups + alternation → catastrophic backtracking
// - Possessive quantifiers (in PCRE / Java / .NET) prevent backtracking; not in JS
// - Anchors (^ $ \b) prune the search; faster matches

// 15) Cross-language differences
//   - JS:          (?<name>) named, (?:) non-capture; modern engines have lookbehind
//   - PCRE:        (?P<name>) for Python compat; (?>...) atomic groups
//   - Python (re): (?P<name>) named groups; (?P=name) backreference
//   - .NET:        (?<name>) and (?'name'); balancing groups for stack matching
//   - Java:        (?<name>) named

// 16) Common bugs
//   • Forgetting non-capturing → group[1] is wrong, indices shift
//   • Greedy ([\s\S]*) eating across multiple matches
//   • Mixing named + numbered references in replace
//   • Backreferences with the g flag — work, but each match resets state
//   • Not anchoring (^ $) for validation regexes

// 17) When to reach for capturing groups
//   - Extracting parts of a structured string (dates, URLs, key=value)
//   - Replacing with parts of the original
//   - Validating + extracting in one pass
//   - Domain-specific parsers where a full parser is overkill
//
// Otherwise: non-capturing is cheaper.

Why it matters

Named groups ((?<year>\d{4})) make regex maintainable — the code reads like the data it’s extracting. Use non-capturing ((?:...)) wherever you’re grouping without needing the captured value.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
/(\d{4})-(\d{2})-(\d{2})/   // capture YMD
/(?:foo|bar)/                  // non-capturing
/(?<year>\d{4})/              // named capture
Try it Yourself »

Exercise

Non-capturing group syntax.

/(? foo|bar)/

Test yourself

Q1. Non-capturing groups use…
Q2. Named groups use…
Q3. Backreferences refer to a previous group via…

Discussion

Loading…