Named groups and non-capturing groups in regex
Parentheses do two jobs: they group and they capture. How non-capturing groups (?:…) group without capturing, how named groups (?<year>…) make captures readable, how to refer back to them with \k<name>, and how Python spells it.

· 4 min read
/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/
match
- 2026-10-13
- 1999-12-31
- 2000-01-01
- 2026-02-28
spare
- 26-10-13
- 2026/10/13
- 2026-1-3
- 13-10-2026
Parentheses in a regex do two jobs at once. They group, so a quantifier or a | applies to everything inside. And they capture, saving the matched text so you can use it later.
Often you only want one of the two. This post covers the two kinds of group that split the jobs apart: non-capturing groups and named groups. Every example is live, so edit the patterns and watch the lines change.
01Plain groups capture
A plain group saves what it matched, numbered from 1 by the position of its opening parenthesis:
const m = "2026-10-13".match(/(\d{4})-(\d{2})-(\d{2})/);
m[0]; // "2026-10-13", the whole match
m[1]; // "2026"
m[2]; // "10"
m[3]; // "13"
That works until the pattern changes. Add a group near the start and every number after it shifts, and m[2] quietly becomes something else.
02Non-capturing groups: (?:…)
Write (?: instead of ( when you need grouping but don't care about the text. Here the group only exists so + repeats "word, then a space":
/^(?:\w+ )+\w+$/Should match2/2
- two words
- one two three
Should not2/2
- single
- double space
It matches exactly like (\w+ )+\w+. The difference is only in what you get back: no capture to number, and nothing to shift when you edit the pattern. It's also a little cheaper, since the engine doesn't save the text.
A good habit: use (?:…) for every group you don't read later. Then the numbered or named groups that remain are the ones that mean something.
03Named groups: (?…)
Give a group a name and read it by name instead of by number:
/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/Should match2/2
- 2026-10-13
- 1999-12-31
Should not3/3
- 26-10-13
- 2026/10/13
- 2026-1-3
const { year, month, day } =
"2026-10-13".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/).groups;
year; // "2026"
Named groups still get numbers too, so m[1] is the year either way. The names just mean the code survives edits to the pattern.
They work in replacements as well, with $<name>:
"2026-10-13".replace(
/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
"$<d>/$<m>/$<y>",
); // "13/10/2026"
04Referring back: \k
A backreference matches the same text a group matched earlier. With named groups, it's \k<name> instead of \1:
/^(?<quote>["']).*\k<quote>$/Should match2/2
- "hello"
- 'hello'
Should not2/2
- "hello'
- 'hello"
That says what it means in a way \1 never does, and it still works if you add groups in front.
05Python spells it differently
Python had named groups first, with a P:
import re
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})", "2026-10")
m.group("year") # "2026"
m.groupdict() # {"year": "2026", "month": "10"}
re.sub(r"(?P<y>\d{4})-(?P<m>\d{2})", r"\g<m>/\g<y>", "2026-10") # "10/2026"
re.match(r"(?P<q>[\"']).*(?P=q)$", '"hi"') # backreference by name
So the same idea is (?<name>…) and \k<name> in JavaScript, but (?P<name>…), (?P=name) and \g<name> in Python. Non-capturing (?:…) is the same everywhere.
06Groups that never repeat-capture
One more surprise: a group inside a quantifier only keeps its last repetition.
"abc".match(/(\w)+/)[1]; // "c", not ["a", "b", "c"]
If you need every repetition, use the g flag with matchAll (or re.findall) on the inner pattern instead.
07Try one
The quotes have to match. Use a group and a reference back to it:
Catch text wrapped in matching quotes: "…" or '…'.
Targets · 0/5 caught
- "hi"target
- 'hi'target
- "it's"target
- 'say "no"'target
- ""target
Innocents · 0 hit
- "hi'innocent
- 'hi"innocent
- hiinnocent
- "hiinnocent
- hi"innocent
Type a regex — the board updates as you go.
08Checklist
( … )groups and captures.(?: … )only groups.- Use
(?: … )for every group you don't read back. (?<name> … )captures by name. Read it with.groups.name, replace with$<name>.\k<name>matches the same text again.- Python:
(?P<name> … ),(?P=name)and\g<name>. - A repeated group keeps only its last repetition.
09Keep practising
- Tricky patterns is a practice pack with plenty of groups.
- The guide's groups section and backreferences go further.