Parentheses (...) in a regex do two jobs at once. They group part of a pattern together so a quantifier or alternation can apply to the whole chunk, and they capture whatever text matched inside them so you can reuse it afterward. That second job is what turns regex from a yes/no matcher into a tool for pulling structured data out of text.
What gets captured
Every pair of parentheses captures the substring it matched, numbered left-to-right by the position of the opening parenthesis. Group 1 is the first (, group 2 is the second, and so on. match[0] (or match.groups in older terminology) is always the entire match — the captures start at index 1.
const dateStr = "2026-07-10";
const match = dateStr.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(match[0]); // "2026-07-10" — the whole match
console.log(match[1]); // "2026" — first group
console.log(match[2]); // "07" — second group
console.log(match[3]); // "10" — third group
String.prototype.match() (without the g flag) and RegExp.prototype.exec() both return this same array-like structure. Note that the pattern only checks the shape of the string — three digits, a dash, two digits, a dash, two digits — it has no idea that month 13 or day 45 aren’t real calendar values.
Backreferences: reusing a capture inside the pattern
A capturing group can also be referenced later in the same pattern with \1, \2, and so on. This is called a backreference, and it requires the exact text already captured to appear again — not the same pattern, the same literal text.
A classic use case is catching accidentally repeated words, like “the the” or “is is”:
const text = "the the cat is is happy";
const repeats = text.match(/\b(\w+) \1\b/g);
console.log(repeats); // ["the the", "is is"]
Replacing with captured groups
Inside a replacement string, captured groups are available as $1, $2, etc. (note the dollar sign, not a backslash — that distinction trips people up constantly). This lets you rearrange matched text instead of just deleting or replacing it wholesale:
const iso = "2026-07-10";
const us = iso.replace(/(\d{4})-(\d{2})-(\d{2})/, "$2/$3/$1");
console.log(us); // "07/10/2026"
| Syntax | Where it’s used | Meaning |
|---|---|---|
(...) |
Inside the pattern | Captures the matched substring as a numbered group |
\1, \2 |
Inside the pattern | Backreference — matches the same text group 1/2 already matched |
$1, $2 |
Inside a replacement string | Inserts the text captured by group 1/2 |
match[1], match[2] |
In JavaScript, after matching | Reads the captured text out of the match result |
Watch out
Adding parentheses always changes the numbering of every group after it, even if you only wanted to group for a quantifier. If you don’t need to reuse the captured text, the next lesson shows a way to group without capturing at all.
Capture the domain from an email address
Write a pattern that matches an email address like user@example.com and captures just the domain (example.com) in group 1.
Show solution
const match = "user@example.com".match(/@([\w.-]+)/);
console.log(match[1]); // "example.com"The @ is a literal anchor for where the domain starts, and [\w.-]+ captures one or more word characters, dots, or hyphens — enough to cover most real-world domains without trying to be a fully spec-compliant email validator.
What’s next
Capturing groups are powerful, but every extra pair of parentheses adds another numbered group to track — even when you never intend to use it. Next up: non-capturing groups, for when you need the grouping but not the capture.