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
| Form | Captures | Notes |
|---|---|---|
(...) | yes, numbered | numbers are assigned left to right by opening paren |
(?:...) | no | grouping only, cheaper, keeps numbering clean |
(?<name>...) | yes, named and numbered | JS, Java, .NET, PCRE, Ruby, Go |
(?P<name>...) | yes | Python's only named form, also valid in PCRE and Go |
(?>...) | no | atomic, no backtracking inside (PCRE, Java, Ruby, Python 3.11+) |
(?#...) | no | inline comment (PCRE, Python, Java) |
(?i:...) | no | flags 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
| Flavor | Numbered | Named |
|---|---|---|
| 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 RE2 | not supported | not 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.
| Form | Effect |
|---|---|
(?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.