Skip to content

Readable patterns

One line: A pattern longer than one line needs the same things code needs — whitespace, comments, names, and pieces that can be reused — and the regex language has all four ((?x), #, (?<name>…), (?(DEFINE)…)) in Perl and PCRE2, most of them in Python, Ruby and Java, and almost none of them in JavaScript and Go, where readability has to come from the host language instead.

Level: 201 · you are about to write a pattern someone else will have to review

The four tools

Tool Does Has it
(?x) free-spacing whitespace ignored, so the pattern can be laid out Python, Perl, Ruby, Java, Rust, PCRE2 — not JavaScript, not Go
# comment under (?x), to end of line; (?#…) anywhere #: the same list; (?#…): Python, Perl, Ruby, PCRE2
(?<name>…) a group with a name, so year instead of 3 all eight — with two spellings that do not overlap (named group)
(?(DEFINE)…) + (?&name) define a sub-pattern once, call it by name Perl, PCRE2 only

Measured

Verified output of topic_readable_patterns_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

construct             python   perl     node  ruby     java     go       rust     fancy
free-spacing (?x)     2026-09  2026-09  -     2026-09  2026-09  -        2026-09  2026-09
comment under (?x)    2026     2026     -     2026     2026     -        2026     2026
(?#…) comment         42       42       -     42       -        -        -        42
named group (?<y>…)   -        2026     2026  2026     2026     2026     2026     2026
named group (?P<y>…)  2026     2026     -     -        -        2026     2026     2026
scoped flag (?i:…)    JANuary  JANuary  n/a   JANuary  JANuary  JANuary  JANuary  JANuary
DEFINE and a call     -        09:30    -     -        -        -        -        -

Reading the table

  • Rows 1–2. Free-spacing and its # comment work in six engines. JavaScript and Go refuse (?x) outright. In those two, the pattern is built from pieces in the host language — which is not a worse answer, just a different one.
  • Row 3. (?#…), the comment that needs no flag: Python, Perl, Ruby and fancy-regex.
  • Rows 4–5. The two name spellings, and the fact that no engine except Perl, Go, Rust and fancy-regex takes both.
  • Row 6, (?i:jan)uary. A scoped flag keeps a case-insensitive part small and visible. JavaScript is n/a because its support arrived after ES2018 and Node 20 and 24 disagree.
  • Row 7, DEFINE. (?x)(?(DEFINE)(?<d>\d\d))^(?&d):(?&d)$ — a two-digit sub-pattern defined once and called twice — matches 09:30 in Perl and nowhere else here. In PCRE2, and so in ABAP, this is how a large pattern gets written as named parts.

When the engine has no (?x)

Build the pattern from named pieces in the language, and let the language's formatter and comments do the work:

JavaScript:  const YEAR = String.raw`(?<year>\d{4})`;
             const re = new RegExp(`^${YEAR}-${MONTH}-${DAY}$`, "u");
Go:          re := regexp.MustCompile(`^` + year + `-` + month + `-` + day + `$`)
Rust:        let re = Regex::new(&format!(r"^{year}-{month}-{day}$")).unwrap();

This works everywhere, including the engines that have (?x), and it has one property (?x) lacks: the pieces can be tested on their own.

The rules

  1. Anything over about forty characters gets (?x) or is assembled from named parts. Both, in a pattern that has to be maintained.
  2. Names, not numbers, for every group that code refers to. Numbers are for (?:…) — that is, for groups nobody refers to.
  3. A comment says what the piece matches, not what the syntax means. # ISO week number, 01–53 is useful; # two digits is the pattern again.
  4. Never add (?x) to an existing pattern without checking every space and # in it — see comment.
  5. A pattern that needs (?(DEFINE)…) to be readable is usually a parser wearing a regex. It is legitimate in PCRE2 and ABAP, where the pattern is the whole program; elsewhere, write the parser.

Keywords on this page

Inline flags · Comment · Named group · Non-capturing group · Conditional — for DEFINE · Recursion and subroutine calls — for (?&name).

See also