Skip to main content
Regex Guide

Regex Capture Groups
How They Work and When to Use Them

Extract exactly the part you need, reference it in replacements, and keep your patterns readable with named groups.

8 min read·Updated May 2026

Regex capture groups — written as (pattern) — do two things at once: they group tokens so you can apply quantifiers to a whole sub-expression, and they capture the matched text so you can extract or reuse it. Understanding groups is the difference between a regex that tells you "match found" and one that hands you the specific data you were after.

Basic Capture Groups

Wrap any part of your pattern in () and you get a numbered capture group. Group 1 is the first opening parenthesis, group 2 is the second, and so on.

// Pattern: extract year, month, day from a date string
const pattern = /(\d{4})-(\d{2})-(\d{2})/;
const match = '2026-05-01'.match(pattern);

match[0]  // '2026-05-01'  — full match
match[1]  // '2026'        — group 1 (year)
match[2]  // '05'          — group 2 (month)
match[3]  // '01'          — group 3 (day)

Non-Capturing Groups (?:)

If you need grouping for alternation or quantifiers but don't need to store the match, use (?:). It keeps your group numbering clean and runs slightly faster.

// Match 'colour' or 'color' — no need to capture the 'u'
/colou?r/           // simple, works fine here
/colo(?:u)?r/       // explicit non-capturing group

// Match http or https — the 's' is optional but not worth capturing
/https?:\/\//       // simpler
/http(?:s)?:\/\//   // non-capturing group version

// Alternation without polluting capture groups
/(?:jpg|jpeg|png|gif)$/i  // matches image extensions

Named Capture Groups

Named groups let you reference matches by a meaningful name instead of a fragile position number. When you add a group or rearrange the pattern, named references still work.

// JavaScript — (?<name>pattern)
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { year, month, day } = '2026-05-01'.match(pattern).groups;
// year='2026', month='05', day='01'

// Python — (?P<name>pattern)
import re
pattern = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})')
m = pattern.match('2026-05-01')
m.group('year')   # '2026'

Capture Groups in Replacements

Groups are most powerful in find-and-replace operations. Reference them in the replacement string to rearrange or reformat matched text.

// Reformat date from YYYY-MM-DD to DD/MM/YYYY
'2026-05-01'.replace(
  /(\d{4})-(\d{2})-(\d{2})/,
  '$3/$2/$1'
);
// Result: '01/05/2026'

// Wrap email addresses in mailto links
text.replace(
  /([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})/g,
  '<a href="mailto:$1">$1</a>'
);

// Python: use \1 or \g<name> in replacement
re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', '2026-05-01')

Backreferences — Match the Same Text Twice

A backreference (\1, \2) matches the exact same string that a previous group captured. This is useful for detecting repeated words, matching balanced quotes, or finding duplicate tokens.

// Match quoted strings where quotes must match
/(['"])(.+?)\1/
// Matches: 'hello'  "world"
// No match: 'mismatched"

// Detect doubled words in text
/\b(\w+)\s+\1\b/gi
// Matches: 'the the'  'is is'  'very Very'

// Match repeated HTML tags
/<(\w+)>.*?<\/\1>/s
// Matches: <p>content</p>  <div>...</div>

Practical Examples

Extract log level and message
/\[(?<level>INFO|WARN|ERROR)\]\s+(?<msg>.+)/
Input: [ERROR] Connection timeout after 30s
Named groups pull level and message into separate variables. Works for any structured log format.
Parse key=value pairs
/(?<key>\w+)=(?<value>[^\s]+)/
Input: host=db.example.com port=5432 user=admin
Iterate all matches with matchAll() to build an object from a config string.
Extract URL components
/^(?<proto>https?):\/\/(?<host>[^\/]+)(?<path>\/.*)?$/
Input: https://api.example.com/v2/users
Named groups for protocol, host and path — no URL parsing library needed for simple cases.
Capture CSV fields (simple)
/(?:^|,)(?:"([^"]*(?:""[^"]*)*)"|([^,]*))/g
Input: "Smith, John",42,engineer
Group 1 catches quoted fields; group 2 catches unquoted. Handles embedded commas inside quotes.

Common Mistakes

Greedy groups consuming too much
Use lazy quantifiers inside groups: (.+?) instead of (.+). Greedy matching will grab as much as possible, often swallowing the delimiter you expected to stop at.
Confusing group number after adding groups
Add a group early in the pattern and all your $2, $3 references shift by one. Switch to named groups to make replacements position-independent.
Using capture groups just for alternation
If you write (cat|dog) just to group the alternation, use (?:cat|dog) instead. It's faster and keeps your match array clean.
Forgetting the global flag with matchAll
String.match() with a capturing group and no g flag returns the full match plus groups. With the g flag it returns only full matches. Use matchAll() to get all matches with their groups.

Test Your Capture Groups

The regex tester shows each capture group's match separately — paste any pattern from this page and see groups highlighted in real time.

Open Regex Tester →

Related