Download Cheat sheet PDF 12 pages · syntax, editors, patterns, Unicode, performance, debugging
Concepts April 27, 2026

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 →