Regex named groups in JavaScript and Python
Numbered groups are fine for two captures. After that, names are clearer, safer, and don't break when you reorganize.
The short answer
JavaScript / PCRE / .NET:
(?<name>pattern) define
\k<name> backreference inside pattern
match.groups.name access in code
Python (stdlib re):
(?P<name>pattern) define
(?P=name) backreference inside pattern
match.group("name") access in code
Why use named groups
Compare these two ways to extract date parts:
Numbered (fragile)
const m = "2024-01-15".match(/^(\d{4})-(\d{2})-(\d{2})$/);
const year = m[1];
const month = m[2];
const day = m[3];
If someone adds a capture group earlier in the pattern, all the numbers shift and the code breaks silently.
Named (clear)
const m = "2024-01-15".match(/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/);
const { year, month, day } = m.groups;
Self-documenting. Pattern reorganization doesn't break access. Better for reviewing in code review.
JavaScript examples
Basic match
const re = /(?<area>\d{3})-(?<exchange>\d{3})-(?<subscriber>\d{4})/;
const m = "555-123-4567".match(re);
console.log(m.groups);
// { area: "555", exchange: "123", subscriber: "4567" }
Replacement with named groups
"John Smith".replace(/(?<first>\w+) (?<last>\w+)/, "$<last>, $<first>")
// "Smith, John"
Backreference
const re = /(?<word>\w+) \k<word>/;
"hello hello".match(re); // matches
"hello world".match(re); // no match
Python examples
Basic match
import re
m = re.match(r"(?P<area>\d{3})-(?P<exchange>\d{3})-(?P<subscriber>\d{4})", "555-123-4567")
print(m.group("area")) # "555"
print(m.groupdict()) # {"area": "555", "exchange": "123", "subscriber": "4567"}
Replacement
re.sub(r"(?P<first>\w+) (?P<last>\w+)", r"\g<last>, \g<first>", "John Smith")
# "Smith, John"
Backreference
re.match(r"(?P<word>\w+) (?P=word)", "hello hello") # matches
Rules for naming
- Names start with a letter or underscore
- Can contain letters, digits, underscores
- Cannot be a reserved word in JavaScript
- Must be unique within the pattern (in most flavors)
Mixing named and unnamed
You can mix them in the same pattern. Named groups still count toward numbering:
(?<area>\d{3})-(\d{3})-(\d{4})
↑ ↑
group 2 group 3
Access:
match.groups.area
match[2], match[3]
Or use non-capturing groups (?:...) for parts you don't want to access by either name or number:
(?<date>\d{4}-\d{2}-\d{2})(?:T\d{2}:\d{2}:\d{2})?
Browser support (JavaScript)
Named groups landed in ES2018. Supported in:
- Chrome 64+ (Jan 2018)
- Firefox 78+ (June 2020)
- Safari 11.3+ (March 2018)
- Node.js 10+
For older targets, fall back to numbered groups. Babel transpiles modern syntax for legacy browsers.
The takeaway
Prefer named groups over numbered, especially when:
- The pattern has more than 2 captures
- The pattern is part of a public API or shared codebase
- You're likely to reorganize the pattern later
- The capture values are passed across function boundaries
The cost is minor (a few extra characters); the benefit is real (clarity, refactor-safety). The only downside is the JS/Python syntax difference — and the flavor converter handles that.
Related reading
Try this pattern in the explainer
Paste any regex into the live explainer and see what each token means, with example matches in real time.
Open the regex explainer →