toolhq.io

All posts
4 min readby Jameel Haider

Named capture groups in regex

A regex that extracts a date into three numbered groups works, until someone inserts a group in the middle and every index shifts by one. Named groups remove that fragility by giving each capture a label that does not depend on position.

Numbered groups and their weakness

Parentheses create a capture group. Groups are numbered left to right by the position of their opening parenthesis, starting at 1.

(\d{4})-(\d{2})-(\d{2})

Against 2026-10-03 this captures 2026 as group 1, 10 as group 2, and 03 as group 3. The code that reads them refers to indexes, and that is the problem. Add a group earlier in the pattern, or wrap something in parentheses for grouping, and every later number moves. Nothing warns you; the extraction just starts returning the wrong field.

Named groups

A named group uses the syntax (?<name>...). The capture still gets a number, but you can now refer to it by a stable label:

(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})

The intent is now readable in the pattern itself, and the names survive reordering.

Referencing them

Named groups are usable in three places:

  • In the match result. In JavaScript, match.groups.year reads the capture. Python exposes m.group("year").
  • In a replacement string. Use $<name> in JavaScript or \g<name> in Python. For example, reformatting a date to day first:
"2026-10-03".replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  "$<d>/$<m>/$<y>"
);
// "03/10/2026"
  • Inside the same pattern, as a backreference with \k<name>, to match the same text again. That technique is covered in backreferences explained.

The readability payoff

The real win is maintenance. Six months later, match.groups.month tells you what the capture holds; match[2] makes you count parentheses and hope. Named groups document the pattern inline, make replacement templates self explanatory, and are immune to the index shuffle that breaks numbered extraction.

A small caution: names must be valid identifiers and unique within the pattern, and support is near universal in modern engines but not in very old ones. If you target an ancient runtime, check first.

You can see both styles side by side in the Regex Tester: write a pattern with named groups, and the capture table shows each group by its name against your sample text. The matching runs entirely in your browser, so the input never leaves your device.

The takeaway: prefer named groups the moment a pattern has more than one capture. They cost a few extra characters and buy you patterns that read like what they mean and code that does not break when the regex grows.