Developer

Capture Groups & Named Groups in Regex

Dev Nexus4 min read

How numbered capture groups, named groups, and non-capturing groups work — and how to read the values they produce.

Matching text is only half of what regular expressions do. The other half is capturing — pulling specific pieces out of a match so you can use them. That is what groups are for.

Parentheses turn part of a pattern into a group. Numbered groups let you refer to captured text by position, named groups give those pieces readable labels, and non-capturing groups let you group without capturing at all. This post explains all three and how to read the values they produce.

The Problem

A pattern that only says "does this match?" throws away the interesting part. If you match a date like 2026-07-18, you almost always want the year, month and day as separate values — not just a yes/no.

Without groups you end up matching the whole thing and then slicing the string by hand, which is fragile. And once you do add groups, a new confusion appears: which number is which? Add an optional group in the middle and every index after it shifts, quietly breaking code that referenced $3.

The Solution

Groups solve both problems. Wrap the parts you want in parentheses and the engine hands them back to you, indexed by position or by name. Named groups in particular make patterns self-documenting and immune to index shuffling.

The clearest way to learn them is to watch the results panel while you build a pattern. In the Regex Tester every match lists its numbered and named groups side by side, so you can see exactly what each set of parentheses captured — all locally in your browser, nothing uploaded.

Step-by-Step Guide

  1. 1

    Numbered capture groups

    Every pair of parentheses creates a capture group, numbered left to right by the position of its opening bracket. For (\d{4})-(\d{2})-(\d{2}) against "undefined-undefined-undefined", group $1 is "undefined", group $2 is "undefined" and group $3 is "undefined". Group (\d{4})-(\d{2})-(\d{2}) is always the entire match. In replacements you refer to them as $1, $2, $3.

  2. 2

    Named capture groups

    Give a group a name with (?<name>...). Rewrite the date as (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}) and you can read year, month and day instead of counting brackets. In JavaScript these appear on match.groups.year, and in replacements you write $<year>. Named groups stay correct even if you add a group earlier in the pattern.

  3. 3

    Non-capturing groups

    Sometimes you need parentheses only to group for a quantifier or alternation, not to capture. Use (?:...). In (?:https?)://(\w+), the (?:https?) groups the protocol for the ? but does not consume a group number — so the domain is still group (?:https?)://(\w+), not group (?:https?). This keeps your indexes clean.

  4. 4

    Nesting and reading order

    Groups can nest. In ((\d{4})-(\d{2})), group undefined is the whole "undefined-undefined", group undefined is "undefined" and group undefined is "undefined" — numbering follows each opening parenthesis left to right, outer before inner. When indexes get hard to track, that is the signal to switch to named groups.

  5. 5

    Backreferences

    You can refer to an earlier group inside the same pattern. (['"]).*?\1 matches a quoted string where the closing quote matches the opening one — \1 reuses whatever group \1 captured. Named groups use \k<name>. This is how you match paired or repeated tokens.

Common Mistakes

  • Miscounting group numbers

    Adding an optional group in the middle shifts every index after it, so $3 suddenly points at the wrong thing. If your pattern has more than two or three groups, use named groups so a new bracket cannot renumber the rest.

  • Capturing when you meant to group

    Using plain (...) purely to apply a quantifier still creates a numbered group and clutters your results. Use the non-capturing (?:...) when you only need grouping, not the captured value.

  • Expecting a value from a group that did not match

    An optional group like (\d+)? returns undefined (not an empty string) when it does not participate in the match. Code that assumes a string will break — always handle the undefined case.

  • Confusing $1 in replacements with \1 in the pattern

    Inside the pattern, a backreference is \1. In the replacement string it is $1. Mixing them up is a common source of literal "\$1" or "$$1" text ending up in your output.

Frequently Asked Questions

What is group 0?

Group 0 is the entire matched text, not a parenthesised sub-part. Your explicit capture groups start at 1 and count up from there by opening-parenthesis position.

When should I use named groups?

Whenever a pattern has several groups or you extract fields you will reference later. Names make the pattern self-documenting and prevent index shifts from breaking downstream code.

What does (?:...) do?

It groups part of a pattern without creating a numbered capture. Use it when you need parentheses for a quantifier or alternation but do not want the captured value or a shifted index.

Why is one of my groups undefined?

An optional group that did not take part in the match returns undefined rather than an empty string. The tester's results panel shows this clearly, which helps you spot which branch matched.

How do I reuse a captured value?

Use a backreference: \1 for numbered groups or \k<name> for named groups inside the pattern, and $1 or $<name> inside a replacement string.

Try the Tool

Regex Tester

See every numbered and named group your pattern captures, live as you type.

Open Regex Tester

Related Tools

Related Articles