Regular Expressions Cheatsheet

Groups and Captures

Use this Regular Expressions reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Group forms

FormCapturesNotes
(...)yes, numberednumbers are assigned left to right by opening paren
(?:...)nogrouping only, cheaper, keeps numbering clean
(?<name>...)yes, named and numberedJS, Java, .NET, PCRE, Ruby, Go
(?P<name>...)yesPython's only named form, also valid in PCRE and Go
(?>...)noatomic, no backtracking inside (PCRE, Java, Ruby, Python 3.11+)
(?#...)noinline comment (PCRE, Python, Java)
(?i:...)noflags scoped to the group

Group 0 is always the whole match. Nesting numbers by opening paren means ((a)(b)) is 1 = ab, 2 = a, 3 = b.

Python does not accept (?<name>...). It is (?P<name>...) there and nothing else. JavaScript is the mirror image, it accepts (?<name>...) and rejects (?P<name>...).

Reading captures out

import re
m = re.search(r'(?P<user>[^@\s]+)@(?P<host>\S+)', 'mail me@example.com now')
m.group(0)        # 'me@example.com'
m.group(1)        # 'me'
m.group('host')   # 'example.com'
m.groups()        # ('me', 'example.com')
m.groupdict()     # {'user': 'me', 'host': 'example.com'}
m.span(1)         # (5, 7)
const m = /(?<user>[^@\s]+)@(?<host>\S+)/.exec("mail me@example.com");
m[0];            // 'me@example.com'
m[1];            // 'me'
m.groups.host;   // 'example.com'
m.index;         // 5
// destructure straight out of the groups object
const { user, host } = m.groups;

A group that did not participate in the match is None in Python and undefined in JavaScript, which is different from an empty string. (a)? on "b" gives a successful match with group 1 unset.

Backreferences inside the pattern

FlavorNumberedNamed
JavaScript\1\k<name>
Python\1(?P=name)
PCRE / PHP\1, \g{1}\k<name>, (?P=name)
Java\1\k<name>
.NET\1\k<name>
Go RE2not supportednot supported

A backreference matches the text the group captured, not the pattern again.

# collapse a doubled word
echo "the the cat sat sat sat" | perl -pe 's/\b(\w+)( \1\b)+/$1/g'
# -> the cat sat
# a quoted string where the quote style must match at both ends
re.findall(r'(["\'])(.*?)\1', """say "hi" and 'bye'""")
# [('"', 'hi'), ("'", 'bye')]

Repeated groups keep only the last iteration

re.search(r'(\d)+', '123').group(1)     # '3', not '123'
re.search(r'(\d+)', '123').group(1)     # '123'
re.findall(r'(\w+)(?:,(\w+))*', 'a,b,c')  # [('a', 'c')]

To collect every repetition you need a loop or finditer, not a repeated group. .NET is the exception, its Group.Captures collection keeps all of them. Python's third-party regex package offers m.captures(1) for the same thing.

Non-capturing groups are the default choice

Use (?:...) unless you actually read the value back. It keeps numbering stable when you add a group later, and it avoids the engine storing offsets you never use.

^(?:https?|ftp)://          protocol alternation, nothing captured
^(?:\d{3}-)?\d{4}$          optional prefix
(?:ab)+                     repeat a sequence

Named groups solve the same fragility better when you do need the value, because inserting a group ahead of them changes no name.

Atomic groups and group scoping

An atomic group (?>...) commits to the first way it can match and never gives characters back. It is the whole-subpattern version of a possessive quantifier.

(?>a+)b     on "aaa"   fails immediately
a+b         on "aaa"   tries every shorter run first

Flag-scoped groups limit a modifier to one subpattern, which beats setting a flag for the whole regex.

FormEffect
(?i:...)case-insensitive inside only
(?-i:...)case-sensitive inside only, even under i
(?i)turn on for the rest of the pattern

Python rejects (?i) anywhere but the very start of the pattern, so use (?i:...) there. JavaScript supports neither and needs a real flag on the literal, (?i) is a syntax error.