EasyDeveloper

Regex

Regex Groups and Capturing: How Grouping Works

Published 2026-08-15 · 8 min read

TL;DR: Parentheses in a regex create capture groups: plain ( ) captures and numbers, (?: ) groups without capturing, (?<name> ) names a capture, and \1 repeats the first capture.

Parentheses in a regular expression do two jobs. The first is grouping, which controls what a quantifier or an alternation applies to. The second is capturing, which stores the text that matched, so a program can pull the username out of an email or the year out of a date. Understanding the difference between the two jobs is what makes patterns like (?<year>\d{4}) readable instead of mysterious. The group syntax used here follows the MDN groups and backreferences reference.

What does a capturing group do?

A pair of plain parentheses captures whatever matched inside. The pattern (\d{4})-(\d{2})-(\d{2}) matches an ISO date and stores the year, month and day in three numbered groups. Quantifiers bind to the group as a whole, so (ab)+ matches ab, abab and ababab instead of a single ab, and (cat|dog) matches either word.

(\d{4})-(\d{2})-(\d{2})   matches 2026-08-15
  group 1 = 2026   group 2 = 08   group 3 = 15

How are groups numbered?

Count the opening parentheses from left to right: the first gets number 1, the second number 2, and so on, even when one group sits inside another. In (a(b)c), group 1 is the outer a(b)c and group 2 is the inner b. Nested groups make the numbering less obvious, which is why many patterns use named groups instead of relying on position.

(a(b)c)   group 1 = the whole match abc
          group 2 = the inner b

What is a non-capturing group?

The form (?:...) groups without storing the text. Use it whenever parentheses are there only for grouping: (?:ab)+ matches abab but creates no capture, so the numbers of the groups that follow are not pushed aside. Keeping non-capturing groups for pure grouping and capturing groups for text you need is what keeps a complex pattern debuggable.

(?:red|blue) car    groups the colors, captures nothing
(\d+)-(\d+)          both captures kept

What are named groups?

A named group, written (?<name>...), captures like a numbered group and also gives it a label. The pattern (?<year>\d{4})-(?<month>\d{2}) captures a year and a month under those names. In code you read match.groups.year instead of remembering that the year is group 1, which survives edits that reorder the pattern. Named groups also make a pattern self-documenting, because the label states what the capture means. Python and modern JavaScript engines support named groups.

(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
  named captures: year, month, day

What are backreferences?

A backreference repeats the text the group matched, not the pattern. \1 refers to group 1, \2 to group 2, and named groups are referenced as \k<name> in many engines. Backreferences are how a pattern matches a pair of identical quotes or tags: the second half must equal whatever the first half matched, which a plain character class cannot express.

(['"]).*?\1      matches "hello" or 'world' with matching quotes
(<b>).*?\1       matches <b>text</b> with the same tag

How do you extract groups in JavaScript?

const m = 'alice@example.com'.match(/^([^@\s]+)@([^@\s]+)$/);
// m[1] = 'alice'   m[2] = 'example.com'

The match result array holds the whole match at index 0 and the groups after it. With the global flag, match returns all full matches and discards groups, so to collect groups use exec in a loop. Named groups are read from m.groups in modern JavaScript.

How do you extract groups in Python?

import re
m = re.search(r'^(?<user>[^@\s]+)@(?<host>[^@\s]+)$', 'alice@example.com')
m.group('user')   # 'alice'
m.group('host')   # 'example.com'

How do you test groups in a pattern?

The fastest way to check which group captures what is a live tester that shows captures per match, and the regex tester highlights each group in a distinct color. Build the pattern with the regex builder when you are composing several groups, and confirm each one with a sample input before wiring it into code.

Frequently Asked Questions

What is the difference between a capture group and a non-capturing group?

Both group a sub-pattern so quantifiers and alternation apply to the whole thing. A capturing group additionally stores the matched text so you can read it back or backreference it. Use (?: ) when you only need grouping, which keeps the group numbering simple.

How are regex groups numbered?

Groups are numbered by counting opening parentheses from left to right, starting at 1, in the order they appear in the pattern. Named groups are numbered too, and they are also addressable by name, which keeps the numbering stable when you edit the pattern.

What is a backreference?

A backreference like \1 matches the exact text that group 1 captured, not the pattern again. It is how you match a repeated value such as a pair of identical quotes: ("[^"]*").*?\1.

How do you get the captured text in code?

In JavaScript use match or exec and read the returned array; index 0 is the full match and 1, 2 are the groups. In Python use re.search and call .group(1). Named groups are read with .groups()['name'] in Python or match.groups.name in some engines.

Why does my group return undefined?

A group can be part of an alternation branch that did not run, or an optional group can match zero times. In JavaScript, an unmatched capture gives undefined in the match array; a group that repeated is stored as its last match.