Groups
Regex Groups — The Complete Guide
Master capturing groups, named groups, non-capturing groups, and backreferences to extract and organize matched text in Python.
Introduction
In regular expressions, groups are portions of a pattern wrapped in parentheses (). They let you treat
multiple characters as a single unit, capture parts of a match for later use, apply quantifiers to a whole sub-pattern, and
reference matched text elsewhere. Groups turn a flat match into structured, labelled data — the key to pulling out exactly the pieces
you need from a string.
Real-World Analogy
Labelled Storage Boxes
Imagine unpacking a shipment: instead of one big pile, you sort items into labelled boxes — "year", "month", "day". Groups do exactly this for a match: each set of parentheses is a box that catches its part of the text, ready to be picked up by number or by name.
Prerequisites
Before You Start
- Python 3.x installed
- Basic understanding of the
remodule (search, match, findall) - Familiarity with common regex tokens (
\d,\w,+,*) - Comfort using raw strings
r"..."for patterns - A Python shell or editor to run the examples
Your First Capturing Group
Wrap part of a pattern in parentheses to capture it, then read it back with .group().
import re
date = "2026-08-16"
m = re.search(r"(\d{4})-(\d{2})-(\d{2})", date)
print(m.group(0)) # 2026-08-16 (entire match)
print(m.group(1)) # 2026 (first group)
print(m.group(2)) # 08 (second group)
print(m.group(3)) # 16 (third group)
0 is always the whole match. Groups are numbered from 1, left to right, by their opening parenthesis.
Types of Groups
Capturing Group (...)
The default — captures and numbers the match.
Everything inside is remembered and retrievable by its position number via .group(n).
Named Group (?P<name>...)
A capturing group with a readable label.
Retrieve it by name with .group("name") instead of a fragile position number.
Non-Capturing Group (?:...)
Groups without capturing.
Use it to apply quantifiers or alternation to a sub-pattern without creating a numbered capture.
Group Syntax Reference
| Syntax | Meaning |
|---|---|
(...) |
Capturing group — numbered from 1 |
(?P<name>...) |
Named capturing group |
(?:...) |
Non-capturing group (groups but doesn't capture) |
(?P=name) |
Backreference to a named group |
\1, \2 |
Backreference to a numbered group |
(?=...) |
Positive lookahead (non-capturing assertion) |
(?!...) |
Negative lookahead |
(?<=...) |
Positive lookbehind |
Named Groups
Named groups make patterns self-documenting and robust against reordering.
import re
pattern = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
m = re.search(pattern, "2026-08-16")
print(m.group("year")) # 2026
print(m.group("month")) # 08
print(m.group("day")) # 16
# groupdict() returns all named groups as a dictionary
print(m.groupdict()) # {'year': '2026', 'month': '08', 'day': '16'}
Non-Capturing Groups
Use (?:...) when you need grouping for logic but don't want to store the result.
import re
# Group "ab" so + repeats it, but don't capture it
m = re.search(r"(?:ab)+(\d+)", "ababab123")
print(m.group(0)) # ababab123 (whole match)
print(m.group(1)) # 123 (only the digits are captured)
Match Object Methods for Groups
| Method | Returns |
|---|---|
m.group(0) |
The entire match |
m.group(n) |
The nth captured group |
m.group("name") |
A named group's value |
m.groups() |
A tuple of all captured groups |
m.groupdict() |
A dict of all named groups |
m.start(n) / m.end(n) |
Start/end index of group n |
m.span(n) |
A (start, end) tuple for group n |
import re
m = re.search(r"(\w+)@(\w+)", "user@domain")
print(m.groups()) # ('user', 'domain')
print(m.span(1)) # (0, 4)
print(m.start(2)) # 5
Backreferences
A backreference matches the same text a previous group captured — great for finding repeats.
import re
# \1 matches whatever group 1 captured — finds doubled words
text = "hello hello world world done"
matches = re.findall(r"\b(\w+)\s+\1\b", text)
print(matches) # ['hello', 'world']
Named backreference
import re
# (?P=word) refers back to the named group "word"
m = re.search(r"(?P<word>\w+) (?P=word)", "bye bye")
print(m.group("word")) # bye
Using Groups in Replacements
Reference captured groups in re.sub() with \1 or \g<name> to reformat text.
import re
# Reformat 2026-08-16 -> 16/08/2026
date = "2026-08-16"
result = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", date)
print(result) # 16/08/2026
# Using named groups in replacement
result2 = re.sub(
r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})",
r"\g<d>/\g<m>/\g<y>",
date,
)
print(result2) # 16/08/2026
Groups Change findall Behaviour
When your pattern has groups, findall returns the groups, not the whole match.
import re
text = "2026-08 and 2027-01"
# No groups -> full matches
print(re.findall(r"\d{4}-\d{2}", text))
# ['2026-08', '2027-01']
# With groups -> tuples of groups
print(re.findall(r"(\d{4})-(\d{2})", text))
# [('2026', '08'), ('2027', '01')]
Counting Groups
Group numbers are assigned by the order of opening parentheses. For a pattern with \(k\) capturing groups, valid indices for
.group() range over:
\[ n \in \{0, 1, 2, \dots, k\} \]
where \(0\) is the full match and \(1 \dots k\) are the captures in left-to-right order. Non-capturing groups (?:...) do not consume a number.
Best Practices
Do This
- Prefer named groups
(?P<name>...)for readability and stability - Use non-capturing groups
(?:...)when you only need grouping, not capturing - Use
groupdict()to turn a match straight into a dictionary - Remember groups change what
findallreturns - Reference groups in
re.subto reformat rather than rebuild strings - Keep group nesting shallow to keep patterns readable
Common Mistakes
findall now returns tuples instead of full matches.
(?:...) if you don't want to change findall's output.
None and can crash later code.
None or give optional groups a sensible default before using them.
Interview Questions
| Question | Short Answer |
|---|---|
| What is a capturing group? | Parentheses that remember the matched text for retrieval by number. |
Difference between (...) and (?:...)? |
The first captures and numbers; the second groups without capturing. |
| How do you name a group? | With (?P<name>...) and read it via group("name"). |
| What is a backreference? | \1 or (?P=name) that matches the same text a group captured. |
What does group(0) return? |
The entire matched substring. |
Quick Revision
| Group | Syntax | Access |
|---|---|---|
| Capturing | (...) |
group(1) |
| Named | (?P<n>...) |
group("n") |
| Non-capturing | (?:...) |
Not captured |
| Backreference | \1 / (?P=n) |
Reuses match |
| All groups | — | groups() / groupdict() |
Key Takeaways
Regex groups turn a flat match into structured data. Use capturing groups to extract,
named groups for clarity, non-capturing groups for pure grouping, and backreferences
to match repeats. Remember: groups change what findall returns, and group(0) is always the full match.