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
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$1is "undefined", group$2is "undefined" and group$3is "undefined". Group(\d{4})-(\d{2})-(\d{2})is always the entire match. In replacements you refer to them as$1,$2,$3. - 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 readyear,monthanddayinstead of counting brackets. In JavaScript these appear onmatch.groups.year, and in replacements you write$<year>. Named groups stay correct even if you add a group earlier in the pattern. - 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
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
Backreferences
You can refer to an earlier group inside the same pattern.
(['"]).*?\1matches a quoted string where the closing quote matches the opening one —\1reuses whatever group\1captured. 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
$3suddenly 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+)?returnsundefined(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.
Related Tools
Related Articles
How to Test a Regex Online
A practical, step-by-step guide to writing and testing a regular expression online with live match highlighting.
Read articleRegex Flags Explained (g, i, m, s, u, y)
What each JavaScript regex flag does — g, i, m, s, u, y — with a short example of what changes when you toggle it.
Read articleAre UUIDs Really Unique?
In practice UUIDs never collide - here is the math behind why, and the one thing people get wrong: a UUID is an identifier, not a secret.
Read article