A capture group is anything wrapped in parentheses. The regex still matches the whole string it always would, but parentheses tell the engine “also remember this specific piece separately.”
pattern: (\d{4})-(\d{2})-(\d{2})
input: "shipped on 2026-09-16"
match: 2026-09-16
group 1: 2026
group 2: 09
group 3: 16The whole match is index 0; groups start at 1. If you only care whether something matched, you don't need groups at all — they exist for when you need to extract or rearrange the pieces.
Named groups make this readable
(?<year>\d{4}) instead of a bare (\d{4}) lets you reference year by name instead of remembering it's “group 1.” Worth doing the moment a pattern has more than two groups, because renumbering groups after inserting a new one earlier in the pattern is a classic way to silently break code that referenced them by position.
Non-capturing groups
Sometimes you need parentheses for grouping/alternation but don't want the match tracked as a numbered group — use (?:...). Example: (?:https?|ftp)://(\w+) groups the protocol alternation without it stealing group 1 from the hostname you actually want.
Replace patterns reuse the groups
Reformatting a date from YYYY-MM-DD to DD/MM/YYYY:
pattern: (\d{4})-(\d{2})-(\d{2})
replacement: $3/$2/$1
"2026-09-16" → "16/09/2026"$1, $2, $3 refer to the groups in the order they were captured, and you can reorder or reuse them freely in the replacement — $1-$1 is valid if you want the first group twice. Named groups use $<year> in the replacement instead of a number.
The gotcha with global flag and replace
Without the g flag, a replace only touches the first match, even if the pattern matches five times in the input. This is the single most common “why didn't my regex replace everything” question, and the fix is always the same: add g.