Skip to content

Regex Cheatsheet

Regular expressions for pattern matching and text processing.

01

Getting Started

Basic Syntax

Regex uses special characters for pattern matching. . matches any char, [] defines character classes, \d \w \s are shorthand for common sets. Use ^ inside [] for negation.

regex
# Basic matching
hello       # literal match
.           # any character except newline
[abc]       # any of a, b, c
[^abc]      # not a, b, or c
[a-z]       # range a to z
\d          # digit [0-9]
\D          # non-digit
\w          # word char [a-zA-Z0-9_]
\W          # non-word char
\s          # whitespace
\S          # non-whitespace

Metacharacters

Metacharacters have special meaning. To match them literally, prefix with a backslash. Inside character classes [], only ] \ ^ and - need escaping (and - only when not at the start/end).

regex
# Special metacharacters that need escaping:
.  *  +  ?  ^  $  {  }  [  ]  \\  |  (  )

# Escape with backslash to match literally
\.           # literal dot
\*           # literal asterisk
\+           # literal plus
\?           # literal question mark
\(           # literal parenthesis
\[           # literal bracket
\$           # literal dollar
\^           # literal caret
\\           # literal backslash

# Example: match "1.5" not "1x5"
1\.5         # matches "1.5" only

Dot & Wildcards

The dot . is the most common wildcard but doesn't match newline by default. Use /s flag (ES2018+) or character class tricks like [\s\S] for cross-engine compatibility when you need to match across lines.

regex
# Dot matches any char except newline (by default)
a.c          # matches "abc", "a c", "a1c", "a@c"
a..c         # four chars: a, any, any, c

# Dot matches newline only with /s flag (dotall)
/a.b/s       # "a\nb" matches

# Match any character truly (with /s flag)
/[\s\S]/     # any char including newline (portable)
/[\d\D]/     # also any char
/[\w\W]/     # also any char

# Practical: any chars between tags
<div[\s\S]*?</div>

Regex Literals in Code

In JS, /pattern/flags is most common. In Python, use raw strings (r"") to avoid double-escaping. Java requires double backslashes in string literals. PHP uses / as delimiter. Go uses backticks or "\\\\" strings.

regex
# JavaScript - regex literal
const re = /\bword\b/gi;

# JavaScript - constructor (for dynamic patterns)
const re2 = new RegExp("\\bword\\b", "gi");

# Python - raw string with re module
import re
pattern = r"\bword\b"
re.search(pattern, "a word here")

# Java - String needs double escaping
Pattern p = Pattern.compile("\\bword\\b");

# PHP - Perl-style
if (preg_match('/\\bword\\b/i', $text)) { }

# Go - RE2 syntax
re := regexp.MustCompile(`\\bword\\b`)

Testing & Matching

JS test() returns boolean, match() returns matches. Python's search() finds anywhere, match() anchors at start, fullmatch() requires entire string to match. Choose the right function for your intent.

regex
# JavaScript
/abc/.test("abcdef")        # true
"abcdef".match(/abc/)       # ["abc", index: 0]
"abcabc".match(/abc/g)      # ["abc", "abc"]
"abcdef".search(/abc/)      # 0 (index)

# Python
import re
re.search(r"abc", "abcdef")  # Match object or None
re.match(r"abc", "abcdef")   # anchored at start
re.fullmatch(r"abc", "abc")  # entire string

# Test if pattern matches anywhere
bool(re.search(r"abc", "xabcx"))  # True
02

Character Classes

Character Sets [abc]

Square brackets define a character set matching any single character listed. Inside [], most metacharacters (. * + ? etc.) lose their special meaning and match literally. Only ] \ ^ and - are special inside.

regex
# Match any single char from the set
[aeiou]      # any vowel
[abc123]     # any of a, b, c, 1, 2, 3
[ABC]        # uppercase A, B, or C (case-sensitive)

# In a set, most metacharacters are literal
[.*+]        # literal . * or +
[()]         # literal ( or )

# Quantifiers work on sets
[aeiou]{2}   # exactly two vowels
[0-9]+       # one or more digits

Negated Sets [^abc]

Placing ^ immediately inside the opening [ negates the set, matching any char NOT listed. The ^ must be first; elsewhere in the set it's a literal caret. Useful for matching 'until X' patterns.

regex
# Caret at start negates the set
[^aeiou]     # any char that is NOT a vowel
[^0-9]       # any non-digit
[^\s]        # any non-whitespace
[^a-zA-Z]    # any non-letter

# Common: match up to a delimiter
[^,]+        # everything until next comma
[^;]+        # everything until next semicolon
[^"\\]      # any char except quote or backslash

# Negation works with quantifiers
[^aeiou]+    # one or more non-vowels

Ranges [a-z]

Hyphen between two chars creates a range based on character codes. Ranges must go from low to high. To match a literal hyphen in a set, put it at the start/end or escape it. Avoid [A-z] which includes non-letter chars between Z and a.

regex
# Hyphen defines a range inside []
[a-z]        # lowercase letter
[A-Z]        # uppercase letter
[0-9]        # digit (same as \d)
[a-zA-Z]     # any letter
[a-zA-Z0-9]  # alphanumeric
[a-fA-F0-9]  # hexadecimal digit

# ASCII order matters
[A-z]        # WARNING: includes [ \ ] ^ _ `

# Multiple ranges in one set
[a-zA-Z0-9_]  # word character (same as \w)

# Literal hyphen: place at start or end, or escape
[-a-z]       # hyphen or lowercase letter
[a-z-]       # lowercase letter or hyphen
[a\-z]       # a, hyphen, or z

Predefined Shorthands

Shorthands \d \w \s are concise and readable. In Unicode mode (default in Python 3, JS with /u), \d and \w match more than ASCII. Use re.ASCII in Python or [0-9] explicitly for ASCII-only.

regex
# Most common shorthands (work outside and inside [])
\d    # digit [0-9]
\w    # word char [a-zA-Z0-9_]
\s    # whitespace [ \t\n\r\f\v]
\D    # non-digit
\W    # non-word char
\S    # non-whitespace

# With ASCII-only flag (Python)
\d      # Unicode digits by default
\d      # [0-9] with re.ASCII flag

# Inside character classes
[\d\s]    # digit or whitespace
[\w.]     # word char or literal dot
[a-z\d]   # letter or digit

# POSIX classes (PCRE, not JS)
[[:alpha:]]  # letters
[[:digit:]]  # digits
[[:alnum:]]  # alphanumeric
[[:space:]]  # whitespace

Negated Shorthands

Uppercase versions \D \W \S negate their lowercase counterparts. They include newlines and other chars. If you need 'non-digit but also not newline', combine explicitly like [^\d\n].

regex
# Uppercase versions are negations
\D    # any non-digit            [^0-9]
\W    # any non-word char        [^a-zA-Z0-9_]
\S    # any non-whitespace       [^\s]

# Useful for "match until X" patterns
\S+        # one or more non-whitespace (a word)
\D+        # one or more non-digits
\W         # a single non-word char

# Combined with anchors
^\S+       # word at start of string
\S+$       # word at end of string

# Find non-word boundaries for tokenization
\W+        # split on punctuation/space

# Caveat: \D matches newlines too
[^\d\n]    # non-digit AND non-newline

Combining Classes

Character classes can mix ranges, shorthands, and literals. Order doesn't matter (except for ranges and ^ at start). Use them to build practical patterns like emails, filenames, and identifiers.

regex
# Mix shorthands, ranges, and literals in []
[\w\s]       # word char or whitespace
[a-z\d_]     # lowercase, digit, or underscore
[\w.-]       # word char, dot, or hyphen (e.g., domain)
[^\w\s]     # neither word char nor whitespace (punctuation)

# Common practical classes
[A-Z][a-z]+       # Capitalized word
\d{1,3}\.\d{1,3}  # version-like number
[\w.+-]+@\w+     # email-ish

# Hex color
#[a-fA-F0-9]{3,6}

# Match identifier (variable name)
[a-zA-Z_$][\w$]*

# Filename with extension
[\w.-]+\.[a-zA-Z]{1,5}
03

Quantifiers

Asterisk * (zero or more)

* matches zero or more of the preceding element. It's greedy by default — matches as much as possible. Add ? after to make it lazy (*?). Commonly combined with ? for 'optional' patterns like https?://

regex
# * matches 0 or more occurrences
ab*c        # "ac", "abc", "abbc", "abbbbc"
\d*         # zero or more digits
\s*         # optional whitespace
[a-z]*      # zero or more lowercase letters

# Useful for optional content
https?://   # http:// or https:// (note the ?)
color:\s*#?[a-f0-9]+   # color: #abc or color: abc

# * is greedy - matches as much as possible
<a.*>       # from <a to LAST > on the line

# Make it lazy with ?
<a.*?>      # from <a to FIRST > (lazy)
.*?         # zero or more, lazy

# * on group
(ab)*       # "", "ab", "abab", "ababab"

Plus + (one or more)

+ requires at least one match (unlike * which allows zero). Useful for 'one or more' patterns like words (\w+), numbers (\d+), or whitespace runs (\s+). Lazy variant +? stops at first match.

regex
# + matches 1 or more occurrences (at least one)
ab+c        # "abc", "abbc", but NOT "ac"
\d+         # one or more digits (integer)
\w+         # one or more word chars (a word)
\s+         # one or more whitespace

# Common patterns
\w+@\w+             # simplistic email
\d+\.\d+           # decimal number
-?\d+               # optional sign + integer
[A-Z]+              # one or more uppercase

# Greedy by default - use +? for lazy
".+?"        # quoted string (lazy: stops at first ")
".+"         # greedy: matches to the LAST "

# + vs *: + requires at least one
a+           # "a", "aa", "aaa" (not "")
a*           # "", "a", "aa", "aaa"

Question ? (zero or one)

? makes the preceding element optional (0 or 1). When placed after another quantifier (*? +? ??), it makes that quantifier lazy. Useful for optional letters, signs, separators, and short/long forms like 'Nov'/'November'.

regex
# ? matches 0 or 1 occurrence (optional)
colou?r     # "color" or "colour"
https?://   # "http://" or "https://"
-?\d+       # integer with optional minus sign
[+-]?\d*    # number with optional sign

# Make greedy quantifier lazy
.*?          # lazy zero-or-more
.+?          # lazy one-or-more
??           # lazy zero-or-one (rarely needed)

# Optional group
Nov(?:ember)?  # "Nov" or "November"
Mr\.?         # "Mr" or "Mr."

# Optional non-capturing group with separator
\d{4}[-/]?(\d{2})  # 2024-01 or 202401

# Chained optionals
a?b?c?       # "", "a", "ab", "abc", "b", "bc", "c"

Curly Braces {n,m}

Curly braces specify exact counts: {n} (exactly n), {n,m} (n to m), {n,} (n or more), {0,m} (up to m). Most engines treat { as literal if not forming a valid quantifier. Always greedy unless ? is added.

regex
# Exact count
\d{3}        # exactly 3 digits
[a-z]{5}     # exactly 5 lowercase letters
.{10}        # exactly 10 of any char

# Range: min, max
\d{2,4}      # 2 to 4 digits
\w{3,}       # 3 or more word chars
.{0,5}       # 0 to 5 of any char (same as.....?)

# Open-ended
\d{3,}       # 3 or more digits
\s{1,}       # one or more whitespace (same as \s+)
\d{0,}       # zero or more (same as \d*)

# Practical patterns
\d{4}-\d{2}-\d{2}        # date YYYY-MM-DD
\d{1,3}(?:\.\d{1,3}){3}  # IPv4 address
\d{3}-\d{3}-\d{4}        # US phone
[A-Fa-f0-9]{6}             # 6 hex digits
\d{4,16}                   # 4-16 digit PIN

# Greedy by default - add ? for lazy
\d{2,4}?    # match minimum (2)

Greedy Quantifiers

Greedy quantifiers consume as much as possible, then backtrack to allow the rest of the pattern to match. This can cause unexpected matches in HTML/text. Use lazy (*? +?) or be more specific with character classes to avoid issues.

regex
# Quantifiers are greedy by default
# They match as MUCH as possible while allowing rest to match
".*"          # in 'a "b" c "d" e', matches "b" c "d"
<a.*>         # matches from <a to the LAST >

# Greedy backtracks to allow overall match
\d+\d        # in "12345", \d+ takes "1234", last \d takes "5"

# Common greedy patterns
.*            # entire line
.+            # entire line (at least 1 char)
\d+           # all consecutive digits
\w+           # all consecutive word chars

# Greedy can be a problem with HTML
<.*>          # WRONG: matches <b>text</b> as one
<\w+>         # better: matches just <b>

# Greedy with anchored end
^.*(end).*$   # .* takes everything, backtracks to find "end"

Lazy Quantifiers

Lazy quantifiers (also called non-greedy or reluctant) match as little as possible, expanding only if needed. Use them to find the shortest match like content between quotes/tags. For quoted strings, '[^']*' is often clearer and faster than '.*?'.

regex
# Add ? to make any quantifier lazy (match as LITTLE as possible)
*?    # lazy zero-or-more
+?    # lazy one-or-more
??    # lazy zero-or-one
{n,m}?  # lazy range

# Lazy matches shortest possible
"<.*?>"      # in 'a "b" c "d" e', matches "b" then "d"
<a.*?>       # matches <a> to first >

# Compare: HTML tag matching
<.+>         # greedy: <b>bold</b> matches as ONE
<.+?>        # lazy: matches <b> then </b> separately

# Find quoted strings (lazy)
"([^"]*)"    # alternative: chars that aren't a quote

# Lazy can be slower in some cases
# Prefer negated classes when possible:
"[^"]*"      # often faster than ".*?"

# Multiple lazy on same line
(.*?)(.*?)(.*?)   # all lazy, but each takes minimum
04

Anchors & Boundaries

Start Anchor ^

^ matches the start of the string (or start of each line with /m flag). It's a zero-width assertion — doesn't consume characters. Don't confuse with ^ inside [], which negates the character class. Use ^...$ to validate entire strings.

regex
# ^ matches position at start of string (or line with /m)
^Hello        # "Hello" only at the start
^\d+          # digits at the start
^https?://    # URL at start of string

# With multiline flag /m, ^ matches start of each line
^\s*$         # blank lines (with /m, matches each)

# Useful for validation (must start with)
^[A-Z]        # must start with uppercase letter
^[a-zA-Z]     # must start with a letter

# ^ inside [] is DIFFERENT (negation)
[^abc]        # any char that is NOT a, b, or c

# Common validation: entire string is digits
^\d+$         # only digits, nothing else

# Line prefix matching (multiline)
^\s*#.*       # comment lines starting with #

End Anchor $

$ matches the end of the string (or end of line with /m). Combine ^...$ to validate that the entire string matches a pattern. Be careful with alternation: ^a|b$ means (^a) OR (b$), use ^(a|b)$ or ^(?:a|b)$.

regex
# $ matches position at end of string (or before final newline)
end$          # "end" only at the end
\.\w{2,4}$    # file extension at end
\d+$          # digits at the end

# With multiline flag /m, $ matches end of each line
\s*$          # trailing whitespace per line (with /m)

# Combine ^ and $ for full-string validation
^\d{4}$        # exactly 4 digits, nothing else
^[A-Z][a-z]+$  # Capitalized word, entire string
^yes|no$       # WARNING: this is (^yes) OR (no$)

# Fix the precedence issue:
^(?:yes|no)$   # entire string is "yes" or "no"

# Email-ish validation
^[\w.+-]+@[\w-]+\.[a-z]{2,}$

# Multiline: find empty lines
^$             # empty lines (with /m)

Word Boundary \b

\b is a zero-width assertion matching the boundary between \w (word char) and \W (non-word char) or string edges. Perfect for matching whole words without matching substrings. Underscore is a word char, so 'foo_bar' is one word to \b.

regex
# \b matches a position between a word char and non-word char
\bword\b      # "word" but not "wording" or "sword"
\bcat         # "cat" at start of word: cat, cats, but not scat
cat\b         # "cat" at end of word: cat, scat, but not cats
\bcat\b       # "cat" as a whole word only

# Useful for finding whole words
\bthe\b       # "the" but not "theater" or "breathe"

# Find words of specific length
\b\w{4}\b      # all 4-letter words
\b\w{4,}\b    # all words with 4+ letters

# Boundary at start/end of identifier
\bUser\b      # whole word "User"
\bid\b         # whole word "id" (not "id" in "ride")

# Variable name as whole word
\bcount\b     # "count" but not "discount" or "counter"

# Boundary between digit and non-digit
\b\d+\b       # whole numbers only

Non-word Boundary \B

\B matches anywhere that is NOT a word boundary. Useful when you want to ensure a pattern is NOT at the start/end of a word. Rare but powerful for specific cases like 'cat' inside another word.

regex
# \B is the opposite of \b - matches where there is NO word boundary
\Bword        # "word" NOT at start of word: "sword", "password"
word\B        # "word" NOT at end of word: "wording", "wordsword"
\Bword\B      # "word" in middle of other word chars

# Find 'cat' inside another word only
\Bcat\B       # matches in " concatenate " (middle 'cat')

# Useful for matching inside identifiers
\Ba\B         # 'a' surrounded by word chars

# Position NOT at word boundary
\B-\B         # hyphen between word chars (e.g., "well-known")

# Practical: hyphenated compound word
\w+\B-\B\w+   # NOT what you want for "well-known"

# \B at start of string with first char being non-word
\B.           # any char that's NOT at a word boundary start

# Test password: 'a' must be inside word (rare)
\Ba\w+\B      # 'a' not at word edges

String Anchors \A \Z \z

\A and \z/\Z are absolute string anchors not affected by multiline mode (Python, PCRE, Ruby, Java). \A is start, \z is end, \Z is end-or-before-final-newline. JS lacks these — use ^ and $ (with /m for line mode). Ruby's ^ $ are always line-based.

regex
# These anchors are NOT in JavaScript natively (use ^ and $)
# Available in Python, Java, PCRE, Ruby, etc.

\A            # absolute start of string (not affected by /m)
\Z            # end of string, or before final newline (Python/PCRE)
\z            # absolute end of string (no newline tolerance)

# Python example
import re
re.search(r"\Ahello", "hello\nworld")  # Match at start
re.search(r"world\Z", "hello\nworld\n")  # Match (before final \n)
re.search(r"world\z", "hello\nworld\n")  # No match (newline at end)

# Difference between ^ and \A with re.MULTILINE
text = "line1\nline2"
re.findall(r"^\w+", text, re.M)  # ['line1', 'line2']
re.findall(r"\A\w+", text, re.M)  # ['line1'] only

# JavaScript: only ^ and $ available
# Use ^ with /m for line starts
# Use $ with /m for line ends

# Ruby always treats ^ and $ as line anchors
# Use \A and \z for string anchors in Ruby
05

Groups & Capturing

Capturing Groups ()

Parentheses () create a capturing group that records the matched text for later use. Groups are numbered left-to-right starting from 1. Use them to extract parts of a match or reference them later in replacement strings or backreferences.

regex
# Parentheses create a capturing group
(abc)        # matches "abc" and captures it
(\d+)        # matches digits and captures them
(a)(b)(c)    # three separate capture groups

# Group with quantifier
(ab)+        # "ab", "abab", "ababab" (whole group repeated)
(\d{2,4})-   # 2-4 digits followed by hyphen

# Capturing in replacement (JS)
"2024-01-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1")
# Result: "15/01/2024"

# Capturing in Python
import re
m = re.search(r"(\w+)@(\w+)", "[email protected]")
m.group(1)  # "user"
m.group(2)  # "example"
m.groups()  # ("user", "example")

# Backreference in same regex
(\w+)\s+\1   # repeated word: "hello hello"

Group Numbering

Groups are numbered by the position of their opening parenthesis, left to right. Outer/nested groups come before their inner groups. Replacement syntax varies: JS/Java use $1, Python uses \1 (or \g<1>), PHP allows both $1 and \1.

regex
# Groups are numbered left-to-right by opening paren
(A)(B)(C)        # group 1=A, 2=B, 3=C
((A)(B))(C)      # group 1=AB, 2=A, 3=B, 4=C

# Nested groups: outer first, then inner
(\d{4})-(\d{2})-(\d{2})    # 1=year, 2=month, 3=day
((\d{1,3})\.){3}(\d{1,3})  # 1=last octet., 2=octet, 4=last octet

# Reference groups in replacement
# JavaScript: $1, $2, $3
"abc".replace(/(a)(b)(c)/, "$3$2$1")  # "cba"

# Python: \1, \2 in re.sub; m.group(1)
re.sub(r"(\w+)@(\w+)", r"\2.\1", "user@host")  # "host.user"

# Java: $1, $2 in replaceAll
"x".replaceAll("(x)", "$1$1")  # "xx"

# PHP: $1 or \1 in preg_replace
preg_replace('/(\w+)@(\w+)/', '$2.$1', 'user@host')

# Whole match: $& (JS), \g<0> (Python), $0 (Java/PHP)

Non-capturing (?:...)

Use (?:...) for grouping when you don't need to capture. It saves memory, keeps your capture group numbering predictable, and signals intent. Especially important when using alternation or quantifiers on sub-patterns where the captured value is irrelevant.

regex
# (?:...) groups WITHOUT capturing (saves memory, cleaner groups)
(?:abc)+         # "abc" repeated, but no capture
(?:\d{1,3}\.){3}\d{1,3}  # IPv4, no extra captures

# Compare: capturing vs non-capturing
(\d{4})-(\d{2})    # 2 groups captured
(?:\d{4})-(?:\d{2}) # 0 groups captured (just match)

# Use non-capturing when you don't need the captured value
https?://(?:www\.)?(\w+)  # capture just the domain
(?:Mr|Mrs|Ms)\.\s+\w+     # title prefix, no capture needed

# Combine with alternation
(?:cat|dog|bird)s?   # plural animals, no capture

# With quantifier on alternation
(?:\d+\s*)+         # numbers separated by spaces

# Why use it?
# 1. Saves memory (no captured text stored)
# 2. Keeps group numbering clean
# 3. Documents intent: "this is just for grouping"

Named Groups (?<name>...)

Named groups (?<name>...) [JS/.NET] or (?P<name>...) [Python/PCRE] make patterns self-documenting. Reference by name in replacement ($<name> in JS, \g<name> in Python) and as backreferences (\k<name> in JS, (?P=name) in Python).

regex
# Named capture group - JavaScript, .NET, Python 3.x style
(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})

# Python uses (?P<name>...) - same as PCRE
(?P<year>\d{4})-(?P<month>\d{2})

# Reference in replacement
# JavaScript:
"2024-01-15".replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  "$<d>/$<m>/$<y>"
)  # "15/01/2024"

# Python:
re.sub(
  r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})",
  r"\g<d>/\g<m>/\g<y>",
  "2024-01-15"
)  # "15/01/2024"

# Reference by name in same regex (backreference)
(?<word>\w+)\s+\k<word>     # JS
(?P<word>\w+)\s+(?P=word)    # Python

# Access named groups in Python
m = re.match(r"(?P<year>\d{4})", "2024")
m.group("year")  # "2024"
m.groupdict()    # {"year": "2024"}

Alternation |

Alternation | matches one of several patterns. It has very low precedence, so use parentheses to scope it. Engines try alternatives left-to-right and pick the first that allows the overall match to succeed — order matters when alternatives overlap.

regex
# | means OR - matches one of several alternatives
cat|dog|bird     # "cat" OR "dog" OR "bird"
yes|no           # "yes" or "no"
\d{2}|\d{4}     # 2 digits OR 4 digits

# IMPORTANT: alternation has low precedence
^abc|def$        # means (^abc) OR (def$) — NOT ^abc$ or ^def$

# Use groups to scope alternation
^(abc|def)$      # entire string is "abc" or "def"
^(?:cat|dog)s?$  # "cat", "cats", "dog", "dogs"

# Order matters - first match wins
a|ab             # in "ab", matches just "a" (leftmost wins)
ab|a             # in "ab", matches "ab"

# Common: file extensions
\.(?:jpg|png|gif|bmp)$

# HTML tags
</?(?:div|span|p|a)\b

# Day names
(?:Mon|Tues?|Wed|Thurs?|Fri|Sat|Sun)day

# Number formats
\d{1,3}(?:,\d{3})*|\d+   # 1,234 or 1234

Group Backreferences

Backreferences (\1, \2 or \k<name>) match the same text that was captured by an earlier group. Classic uses: matching HTML/XML tag pairs, finding repeated words, ensuring consistent separators. The backreference must match exactly what the group captured.

regex
# \1, \2, etc. refer to a previous capturing group's match
(\w+)\s+\1        # repeated word: "hello hello"
<(\w+)>.*?</\1>    # HTML tag matching: <div>...</div>
(["\'])[^\1]*\1   # quoted string (quote char remembered)

# Named backreferences
# JavaScript: \k<name>
(?<q>["\'])[^\k<q>]*\k<q>   # quoted string with named ref

# Python: (?P=name)
(?P<q>["\'])[^\2]*?(?P=q)    # similar (numbered ref still works)

# Practical: matching opening/closing tags
<b>(.*?)</\1>    # WARNING: \1 is "b", but...

# Better: capture tag name
<([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1>

# Find duplicated words
\b(\w+)\s+\1\b   # "the the", "is is"

# Match paired delimiters
(\[)[^\]]*\1\]    # WRONG: matches "[[..[" — use \1 for SAME char

# Date with same separator
(\d{4})([-/])(\d{2})\2(\d{2})  # 2024-01-15 (same separator)
06

Non-capturing Groups

Basic Non-capturing

(?:...) groups without creating a capture. Use it whenever you need grouping for alternation or quantifiers but don't care about the matched value. Keeps group numbering predictable and slightly faster.

regex
# (?:pattern) - group without capturing
(?:abc)         # matches "abc", no capture
(?:foo|bar)     # "foo" or "bar", no capture

# Compare
(\d{4})-(\d{2})-(\d{2})     # 3 capture groups
(?:\d{4})-(?:\d{2})-(?:\d{2})  # 0 capture groups (just match)

# When you only care about match/not-match
^(?:Yes|No|Maybe)$       # validate yes/no/maybe
https?://(?:www\.)?\w+  # URL prefix, only domain matters

# Keep capture numbers clean
(?:https?://)?(\w+)    # group 1 = domain (prefix is non-capturing)
vs
(https?://)?(\w+)      # group 1 = protocol, group 2 = domain

# Nested
(?:(?:foo|bar)-)?(\w+)  # group 1 = word (after optional prefix)

With Quantifiers

Non-capturing groups combine well with quantifiers when you want to repeat a sub-pattern without keeping the matched text. This is essential for keeping capture group numbers predictable when extracting specific parts of a complex pattern.

regex
# Apply quantifier to a non-capturing group
(?:abc)+          # "abc", "abcabc", "abcabcabc"
(?:\d{1,3}\.){3}\d{1,3}  # IPv4: 3 groups of "ddd." then "ddd"
(?:\s*,\s*)      # comma with optional surrounding whitespace
(?:\w+\s+)+\w+\.?  # word sequence (sentence-ish)

# Compare: capturing group + quantifier keeps LAST match only
(\d,)+           # in "1,2,3,", group 1 captures "3," (last)
(?:\d,)+         # same match, no capture

# Optional group with separator
\d{4}(?:-\d{2}){2}    # 2024-01-15 (no captures)
\d{4}(?:[-/]\d{2}){2} # 2024-01-15 or 2024/01/15

# Repeated pattern
(?:\d+\.)*\d+        # version number like 1.2.3

# Multiple non-capturing groups
^(?:Mr|Mrs|Ms|Dr)\.?\s+(?:[A-Z][a-z]+\s?)+$  # full name w/ title

# Avoid extra captures when extracting specific data
https?://(?:www\.)?([\w-]+)\.(?:com|org|net)  # only capture domain

Nested Groups

Mix non-capturing groups freely with capturing ones. Captures are still numbered by opening-paren order, but non-capturing groups don't consume a number. Use this to keep your capture numbers tight when only some parts matter.

regex
# Mix capturing and non-capturing groups
(?:(\d{4})-(\d{2})-(\d{2}))  # outer non-cap, 3 inner captures
# group 1=year, 2=month, 3=day

# With alternation inside non-capturing
(?:https?|ftp)://([^/]+)  # group 1 = host

# Date with optional time
(\d{4}-\d{2}-\d{2})(?:[ T](\d{2}:\d{2}))?
# group 1 = date, group 2 = optional time

# Practical: parse URL
(?:([^:]+)://)?     # group 1: optional scheme
([^/?#]+)           # group 2: host
(?:/([^?#]*))?      # group 3: optional path
(?:\?([^#]*))?      # group 4: optional query
(?:#(.*))?          # group 5: optional fragment

# CSV-ish parser (simplified)
(?:^|,)(?:"([^"]*)"|([^,]*))
# group 1: quoted field, group 2: unquoted field

# Email-ish
(?:[\w.+-]+)@((?:[\w-]+\.)+[a-z]{2,})
# group 1 = domain (non-capturing group inside)

Performance Benefits

Non-capturing groups (?:...) are slightly faster and use less memory because the engine doesn't store the captured text. The benefit is small per match but can matter in tight loops over large text. Use them whenever you don't need to reference the captured value.

regex
# Non-capturing groups are slightly faster
# They skip storing the matched text

# Microbenchmark concept (Python)
import re, time
text = "a" * 1000

# Capturing version
r1 = re.compile(r"(a)+")
# Non-capturing version
r2 = re.compile(r"(?:a)+")
# Both match same text, but r2 is marginally faster

# More importantly: avoids unnecessary group data
# Use non-capturing when you don't need group(N)

# In hot loops, this matters:
patterns = [
    r"(?:\s*,\s*)",        # non-capturing
    r"(\s*,\s*)",          # capturing (waste if unused)
]

# When extraction IS needed, use capturing:
re.findall(r"(\w+)@(\w+)", "a@b c@d")  # need the captures

# When only matching matters, use non-capturing:
re.search(r"(?:\d{1,3}\.){3}\d{1,3}", text)  # just match test

# Replace with no group reference
re.sub(r"(?:\s)+", "_", "a  b   c")  # "a_b_c" (no groups used)

Common Use Cases

Non-capturing groups shine for: grouping alternation, applying quantifiers to sub-patterns, optional sections, and keeping capture numbers clean. Default to (?:...) for grouping — only use () when you actually need to extract or backreference the text.

regex
# 1. Grouping for alternation
(?:cat|dog|bird)s?         # plural animal names
\.(?:jpg|jpeg|png|gif)$    # image extensions
^(?:Mr|Mrs|Ms|Dr)\.\s     # honorifics

# 2. Optional/required sub-patterns
https?://(?:www\.)?       # protocol + optional www
\d{4}(?:-?\d{2}){2}      # date with optional hyphens

# 3. Repeated complex sub-pattern
(?:\d{1,3}\.){3}\d{1,3}  # IPv4
(?:\s*\d+\s*)+,          # comma-separated numbers

# 4. Path-style patterns
(?:/[a-z][\w-]*)+         # URL path like /api/users/123
(?:\.\w+)+               # file.txt.bak (multiple extensions)

# 5. Quoted strings with escapes
"(?:[^"\\]|\\.)*"      # double-quoted with escapes

# 6. Header parsing
^(\w+):\s*(.*)$          # name: value (just capture both)

# 7. Programming keyword + identifier
(?:const|let|var)\s+(\w+)  # only capture the variable name

# 8. CSV with optional quotes
(?:^|,)(?:"([^"]*)"|([^,]*))  # capture quoted OR unquoted
07

Lookahead & Lookbehind

Positive Lookahead (?=)

Positive lookahead (?=...) asserts that what follows matches the pattern, without consuming characters. Useful for finding patterns only when followed by something specific, and for validation like 'must contain a digit' in password rules.

regex
# (?=...) - matches if pattern follows, WITHOUT consuming
foo(?=bar)        # "foo" only if followed by "bar"
\d+(?=px)         # digits only if followed by "px"
\w+(?=@)          # word chars only if followed by "@"

# Lookahead doesn't consume - the next match starts after "foo"
"foobar".match(/foo(?=bar)/)  # match: "foo", index 0
# Next search continues at index 3, "bar" is still there

# Multiple lookaheads (all must match)
\d+(?=\s)(?=.*\bUSD\b)   # digit followed by space AND USD somewhere

# Password: must contain a digit
^(?=.*\d).{8,}$    # at least 8 chars, contains a digit

# Word boundary alternative using lookahead
\w+(?=\W|$)        # word chars until non-word or end

# Find numbers not followed by units
\d+(?!px)(?!em)    # number not followed by px or em

# Capture inside lookahead
(\w+)(?=(\W))     # word + capture the following non-word char

Negative Lookahead (?!)

Negative lookahead (?!...) asserts that what follows does NOT match. Common uses: excluding specific words, rejecting patterns (like 'password must not contain password'), and matching only when something doesn't follow (e.g., 'q' not followed by 'u').

regex
# (?!...) - matches if pattern does NOT follow
foo(?!bar)        # "foo" only if NOT followed by "bar"
\d+(?!px)         # digits NOT followed by "px"
\w+(?!@\w)        # word NOT followed by @word

# Q-w-o-T: find "q" not followed by "u"
q(?!u)            # matches "q" in "Iraq" but not "quiet"

# Negative lookahead for "not this word"
\b(?!and\b|or\b|not\b)\w+  # any word except "and", "or", "not"

# Number NOT followed by decimal
\d+(?!\.\d)      # "100" in "100 " but not in "100.5"

# Email local part: no consecutive dots
^(?!.*\.\.)[\w.]+@  # reject "a..b@..."

# Password: must NOT contain "password"
^(?!.*password).{8,}$   # 8+ chars, no "password" substring

# Disallow specific file extensions
\.\w+(?!\.(?:exe|bat|dll)$)  # not an executable extension

# Find "cat" not preceded by "scat" (would need lookbehind)
# but here: cat not FOLLOWED by "s" or "egory"
\bcat(?!s|egory)\b

Positive Lookbehind (?<=)

Positive lookbehind (?<=...) asserts that what precedes matches, without consuming. Used to find patterns only when after something specific. Many engines (Java, older PCRE, Python < 3.7) require fixed-length lookbehind; JS (ES2018+), .NET, and newer PCRE allow variable length.

regex
# (?<=...) - matches if pattern PRECEDES (without consuming)
(?<=\$)\d+        # digits preceded by "$"
(?<=#)\w+          # word chars preceded by "#" (hex color)
(?<=Mr\.)\s\w+    # word after "Mr."

# Lookbehind doesn't consume - match starts AFTER the prefix
"$100".match(/(?<=\$)\d+/)  # match: "100", index 2

# Get value after specific label
(?<=total:\s*)\d+\.\d{2}   # number after "total:"

# Find text inside quotes
(?<=")[^"]+(?=")   # content between double quotes

# Currency amount
(?<=USD\s)\d+(?:\.\d{2})?  # number after "USD "

# Capture domain after @
(?<=@)[\w.]+      # domain part of email

# Path segment after /api/
(?<=/api/)[^/?#]+  # resource name in URL

# IMPORTANT: many engines require FIXED-LENGTH lookbehind
# (?<=ab|abc)  -- INVALID in some engines (variable length)
# (?<=ab|cd)   -- OK in most (same length alternatives)
# (?<=\w+)    -- INVALID (variable length) in old engines

# JavaScript (ES2018+), .NET, and newer PCRE allow variable length

Negative Lookbehind (?<!)

Negative lookbehind (?<!...) asserts that what precedes does NOT match. Useful for excluding matches based on context, like 'find numbers not preceded by $' or 'find a word not part of a larger identifier'. Same length restrictions as positive lookbehind in older engines.

regex
# (?<!...) - matches if pattern does NOT precede
(?<!\$)\d+        # digits NOT preceded by "$"
(?<!\w)\w+        # word NOT preceded by word char (start of word)
(?<!Mr\.)\s\w+   # space+word NOT after "Mr."

# Find "cat" not preceded by "scat" or "concat"
(?<!s)cat\b       # "cat" not preceded by "s" (matches "cat" alone)

# Number not preceded by a sign
(?<![-+])\d+      # unsigned numbers

# Word not after specific prefix
(?<!un)\w+        # word not preceded by "un"

# Currency validation
(?<!\d)\d{1,3}(?:,\d{3})*(?:\.\d{2})?(?!\d)
# number with thousand separators, not part of larger number

# Prevent matching substring inside larger identifier
(?<![_\w])count\b(?!er)  # "count" not part of "counter"/"_count"

# Remove spaces NOT after periods
text.replace(/(?<!\.)  +/g, " ")
# collapse double spaces unless after a period

# Find "and" not preceded by "&" (avoid matching "&" alone)
(?<!&)\b\w+\b

Validation Patterns

Lookaheads excel at validation: each lookahead checks one rule from the same starting position. Stack multiple lookaheads at the start (^) to enforce compound rules like 'password must have uppercase, digit, special char'. The main pattern then matches the whole string.

regex
# Lookarounds are perfect for "must contain" / "must not contain"

# Password: 8+ chars, 1+ uppercase, 1+ lowercase, 1+ digit
^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$

# Stronger: also require special char
^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$

# Must NOT contain "password" (case-insensitive)
^(?!.*password).{8,}$

# Email: must have @ and a dot after it
^(?=[^@]*@[^@]*\.[^@]*$).+$

# US phone: 10 digits possibly with separators
^(?=(?:\D*\d){10}\D*$)[\d\s()-]+$

# IPv4: 4 octets 0-255
^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$

# Hex color: 3 or 6 hex digits, optional #
^#?(?:[0-9a-fA-F]{3}){1,2}$

# Date: YYYY-MM-DD with basic validation
^(?!0000)\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$

# Username: 3-20 chars, alphanumeric + underscore, not starting with digit
^(?=[A-Za-z_])[A-Za-z0-9_]{3,20}$

Lookaround Limitations

Lookbehind limitations: many engines require fixed-length patterns (Python <3.7, Java, older PCRE). Modern engines (JS ES2018+, .NET, Python 3.7+, PCRE 4.5+) allow variable length. Lookarounds are zero-width — they don't consume or advance the cursor. Capture groups inside them DO capture.

regex
# 1. Lookbehind must be FIXED LENGTH in many engines
# Python (before 3.7), Java, older PCRE
(?<=abc|abcd)x    # INVALID - alternatives of different lengths
(?<=abc|def)x     # OK - same length

# Python 3.7+, JS (ES2018+), .NET, PCRE 4.5+ allow variable length
(?<=\w+)x        # OK in modern engines

# 2. Lookbehind CANNOT be unbounded
(?<=.*)x         # INVALID in most engines (no quantifier)

# 3. Capture groups inside lookaround
# DO capture, but the lookahead itself doesn't consume
(\w+)(?=(\W))   # group 1 = word, group 2 = next non-word

# 4. JavaScript lookbehind support (ES2018+)
# Old Safari versions don't support lookbehind
# Use capturing alternative for old browsers:
# Instead of (?<=\$)\d+, use \$(\d+) and use group 1

# 5. Lookarounds are zero-width
# They don't move the cursor
"abc".match(/(?=a)a/)   # match: "a"
"abc".match(/(?=a)b/)   # no match (can't be both a and b)

# 6. Nested lookarounds (legal but complex)
(?=(?:.*\d){3}).+   # must contain at least 3 digits

# 7. Performance: lookarounds can be slow with catastrophic patterns
(?=(a+)+)b           # avoid nested quantifiers in lookahead
08

Substitution & Replacement

Basic Replacement

Replacement syntax varies by language: JS uses string.replace(regex, str), Python uses re.sub(pattern, repl, string), Java uses String.replaceAll, PHP uses preg_replace. Use the global flag (g) to replace all matches; otherwise only the first is replaced.

regex
# Replace in different languages

# JavaScript
"hello world".replace(/world/, "there")     # "hello there"
"hello world".replace(/o/g, "0")            # "hell0 w0rld"

# Python
import re
re.sub(r"world", "there", "hello world")    # "hello there"
re.sub(r"o", "0", "hello world")            # "hell0 w0rld"

# Java
"hello world".replaceAll("o", "0")          # "hell0 w0rld"

# PHP
preg_replace('/o/', '0', 'hello world')     # "hell0 w0rld"

# Replace first occurrence only
"hello world".replace(/o/, "0")             # "hell0 world" (JS)
re.sub(r"o", "0", "hello world", count=1)   # "hell0 world" (Py)

# Whole-match reference: $& (JS), $0 (Java/PHP), \g<0> (Py)
"abc".replace(/\w/g, "$&!")                # "a!b!c!"

# Python: use a function
re.sub(r"\w", lambda m: m.group(0) + "!", "abc")  # "a!b!c!"

Capture Group Replacement

Reference capture groups in replacements: JS/Java use $1, $2; Python uses \1, \2 or \g<1> (preferred); PHP allows $1 or \1. Use $& (JS) / $0 (Java) / \g<0> (Python) for the whole match. To put a literal $ in JS replacement, use $$.

regex
# Reference captured groups in replacement

# JavaScript: $1, $2, ... for groups; $& for whole match
"2024-01-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1")
# Result: "15/01/2024"

# Swap two words
"hello world".replace(/(\w+) (\w+)/, "$2 $1")  # "world hello"

# Python: \1, \2 OR \g<1>, \g<2> (preferred for clarity)
re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "2024-01-15")
# Result: "15/01/2024"

# Java: $1, $2
"2024-01-15".replaceAll("(\d{4})-(\d{2})-(\d{2})", "$3/$2/$1")

# PHP: $1 or \1
preg_replace('/(\d{4})-(\d{2})-(\d{2})/', '$3/$2/$1', '2024-01-15')

# .NET: ${name} for named groups
# "John".Replace(...) uses ${first} if pattern is (?<first>\w+)

# Use $& (JS) or $0 (Java) for entire match
"hello".replace(/\w/g, "[$&]")    # "[h][e][l][l][o]"

# Literal $ in replacement (JS) - use $$
"price".replace(/price/, "$$100")  # "$100"

Named Group Replacement

Named groups in replacements: JS uses $<name>, Python uses \g<name>, Java/.NET use ${name}. PHP doesn't support named refs in replacement — use numbered ($1, $2). Named replacements are far more readable, especially for complex patterns with many groups.

regex
# Reference named capture groups in replacement

# JavaScript: $<name>
"2024-01-15".replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  "$<d>/$<m>/$<y>"
)  # "15/01/2024"

# Python: \g<name>
re.sub(
  r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})",
  r"\g<d>/\g<m>/\g<y>",
  "2024-01-15"
)  # "15/01/2024"

# .NET: ${name}
Regex.Replace("2024-01-15",
  @"(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})",
  "${d}/${m}/${y}")

# PHP: \k<name> in pattern, but in replacement use \1 or $1
# (PHP doesn't directly support named backrefs in replacement)
preg_replace(
  '/(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})/',
  '$3/$2/$1',  # use numbered refs
  '2024-01-15'
)

# Java: ${name} (Java 7+)
"2024-01-15".replaceAll(
  "(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})",
  "${d}/${m}/${y}")

# Swap using named groups
"John Doe".replace(/(?<first>\w+) (?<last>\w+)/, "$<last>, $<first>")
# "Doe, John"

Replace All Occurrences

Replace-all semantics differ: JS requires /g flag (or use replaceAll), Python's re.sub does all by default (use count= to limit), Java's replaceAll does regex/all while replace does literal/all, PHP preg_replace does all by default (use $limit). Always check whether your replace does first-only or all.

regex
# Replace ALL matches - critical difference between languages

# JavaScript: needs /g flag for replaceAll behavior
"a-b-c".replace(/-/g, "+")           # "a+b+c"
"a-b-c".replace(/-/, "+")            # "a+b-c" (only first!)

# JavaScript: .replaceAll (ES2021+) requires /g flag
"a-b-c".replaceAll("-", "+")         # "a+b+c" (string pattern)
"a-b-c".replaceAll(/-/g, "+")        # "a+b+c" (regex needs /g)

# Python: re.sub replaces ALL by default
re.sub(r"-", "+", "a-b-c")           # "a+b+c"
# Use count parameter for limited replacement
re.sub(r"-", "+", "a-b-c", count=1)  # "a+b-c"
re.sub(r"-", "+", "a-b-c", count=2)  # "a+b+c" wait, just first 2

# Java: replaceAll takes a regex
"a-b-c".replaceAll("-", "+")         # "a+b+c"
# replaceFirst for just one
"a-b-c".replaceFirst("-", "+")       # "a+b-c"
# Note: String.replace (no "All") does literal replace of ALL
"a-b-c".replace("-", "+")            # "a+b+c" (literal, not regex)

# PHP: preg_replace replaces all by default
preg_replace('/-/', '+', 'a-b-c')    # "a+b+c"
# Use $limit parameter
preg_replace('/-/', '+', 'a-b-c', 1) # "a+b-c"

# Go (noreplaceAll in regexp package)
re := regexp.MustCompile("-")
re.ReplaceAllString("a-b-c", "+")    # "a+b+c"

Function Replacement

Function replacements enable dynamic transformations: case conversion, arithmetic, lookups. JS passes (match, groups, offset, string) to the function. Python passes a match object — use .group(n) to access captures. Java uses Matcher.appendReplacement; PHP uses preg_replace_callback.

regex
# Use a function for dynamic replacement (JS, Python)

# JavaScript: function receives (match, g1, g2, ..., offset, string)
"hello world".replace(/\w+/g, w => w.toUpperCase())
# "HELLO WORLD"

# Reverse each word
"hello world".replace(/\w+/g, w => w.split("").reverse().join(""))
# "olleh dlrow"

# Increment numbers
"a1 b2 c3".replace(/\d/g, d => parseInt(d) + 1)
# "a2 b3 c4"

# Python: function receives a match object
import re
re.sub(r"\w+", lambda m: m.group(0).upper(), "hello world")
# "HELLO WORLD"

# Use captured groups in Python function
def swap(m):
    return f"{m.group(2)} {m.group(1)}"
re.sub(r"(\w+) (\w+)", swap, "hello world")  # "world hello"

# Java: Matcher.appendReplacement with StringBuilder
Matcher m = Pattern.compile("\\d+").matcher("a1 b2");
StringBuilder sb = new StringBuilder();
while (m.find()) {
    m.appendReplacement(sb, String.valueOf(Integer.parseInt(m.group()) + 10));
}
m.appendTail(sb);  # "a11 b12"

# PHP: preg_replace_callback
preg_replace_callback('/\d+/', function($m) {
    return $m[0] + 10;
}, 'a1 b2');  # "a11 b12"

Common Replacement Patterns

Common regex replacements: trim whitespace, collapse spaces, strip HTML, case conversion (kebab↔camel↔snake), masking sensitive data, formatting numbers/phones, and escaping metacharacters. Each uses capture groups and replacement references for clean, declarative transforms.

regex
# Trim leading/trailing whitespace
"  hi  ".replace(/^\s+|\s+$/g, "")        # "hi"

# Collapse multiple spaces into one
"a   b    c".replace(/\s+/g, " ")         # "a b c"

# Replace newlines with <br>
"text\nmore".replace(/\n/g, "<br>")      # "text<br>more"

# Strip HTML tags
"<p>Hello</p>".replace(/<[^>]+>/g, "")     # "Hello"

# Convert kebab-case to camelCase
"my-variable".replace(/-([a-z])/g, (_, c) => c.toUpperCase())
# "myVariable"

# CamelCase to snake_case
"myVar".replace(/([A-Z])/g, "_$1").toLowerCase()
# "my_var"

# Escape regex metacharacters
"1.5".replace(/[.*+?^${}()|[\]\\]/g, "\\$&")  # "1\\.5"

# Mask emails: hide local part
"user@host".replace(/(^.).+(@.+)/, "$1***$2")  # "u***@host"

# Format phone: 1234567890 -> 123-456-7890
"1234567890".replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")

# URL-encode unsafe characters
"hello world".replace(/[^A-Za-z0-9]/g, c => "%" + c.charCodeAt(0).toString(16))
# "hello%20world"
09

Flags

Global g

The g flag (JS) makes match() and replace() work on ALL matches, not just the first. Note: g makes regex stateful via lastIndex — reusing the same regex object across exec() calls can cause bugs. Use matchAll (ES2020+) for safer iteration. Python uses re.findall / re.finditer; Java uses replaceAll.

regex
# g flag - match ALL occurrences, not just the first

# JavaScript
"abcabc".match(/a/g)       # ["a", "a"] (all matches)
"abcabc".match(/a/)        # ["a"] (first match only)
"a-b-c".replace(/-/g, "+") # "a+b+c"

# Without /g, replace() only does first match
"a-b-c".replace(/-/, "+")  # "a+b-c"

# With /g, .match returns array of all matches (or null)
"aaa".match(/a/g)          # ["a", "a", "a"]

# WARNING: /g with exec() maintains lastIndex state (gotcha)
const re = /a/g;
re.exec("aaa")  # index 0, lastIndex=1
re.exec("aaa")  # index 1, lastIndex=2
re.exec("aaa")  # index 2, lastIndex=3
re.exec("aaa")  # null, lastIndex=0 (resets)

# Use String.matchAll (ES2020+) for safer iteration
const matches = "aaa".matchAll(/a/g);
for (const m of matches) console.log(m.index);

# Python (re.sub finds all by default; re.findall)
re.findall(r"a", "aaa")    # ["a", "a", "a"]
re.finditer(r"a", "aaa")   # iterator of match objects

# Java: replaceAll = with /g; replaceFirst = without
# PHP: preg_match_all = with /g; preg_match = without
preg_match_all('/a/', 'aaa', $m)  # finds all

Case-insensitive i

The i flag makes matching case-insensitive. Use inline (?i) for scoped case-insensitivity (e.g., abc(?i)def makes only 'def' case-insensitive). Most engines support (?i:...) for inline scoped flags. Common for HTML tags, emails, and case-insensitive search.

regex
# i flag - ignore case
/abc/i.test("ABC")      # true
/abc/i.test("AbC")      # true

# Match HTML tags case-insensitively
/<div>/i.test("<DIV>")   # true
text.replace(/<br\s*\/?>/gi, "\n")  # <br>, <BR>, <Br/>

# Case-insensitive word match
"Hello hello HELLO".match(/hello/gi)  # ["Hello", "hello", "HELLO"]

# Email is case-insensitive in practice
"[email protected]".match(/^[\w.+-]+@[\w.-]+\.[a-z]{2,}$/i)

# Python
re.search(r"abc", "ABC", re.IGNORECASE)  # match
re.IGNORECASE  # constant
re.I           # shorthand

# Java
"ABC".matches("(?i)abc")  # true (inline flag)
Pattern.compile("abc", Pattern.CASE_INSENSITIVE)

# Inline flags (most engines): (?i)
# (?i)abc  ==  /abc/i
# abc(?i)def  == abc plus case-insensitive def
# (?i:abc)  == case-insensitive abc (PCRE/Java, scoped)

# PHP: '/abc/i'
preg_match('/abc/i', 'ABC')  # 1 (match)

# Go (RE2): always use FlagI
re := regexp.MustCompile("(?i)abc")
re.MatchString("ABC")  # true

# .NET
Regex.Match("ABC", "abc", RegexOptions.IgnoreCase)

Multiline m

The m flag makes ^ and $ match at each line boundary (newline), not just the string start/end. Useful for processing line-based content. Inline form is (?m). Note: m does NOT affect the dot . — that's the s (dotall) flag's job.

regex
# m flag - ^ and $ match start/end of EACH LINE (not just string)

# Without /m: ^ and $ are string-anchored
"line1\nline2".match(/^\w+/)   # ["line1"] (only first line)
"line1\nline2".match(/^\w+/gm) # ["line1", "line2"] (each line)

# Find empty lines
"text\n\nmore".match(/^$/gm)  # [""] (the empty line)

# Comment lines in code
"^#.*" with /gm finds all lines starting with #

# Process each line: replace leading whitespace
"  a\n b\n  c".replace(/^\s+/gm, "")
# "a\nb\nc"

# JavaScript
"hello\nworld".replace(/^/gm, "> ")
# "> hello\n> world"

# Python (re.MULTILINE)
re.findall(r"^\w+", "line1\nline2", re.MULTILINE)
# ["line1", "line2"]
re.M  # shorthand

# Java: Pattern.MULTILINE
Pattern p = Pattern.compile("^\w+", Pattern.MULTILINE)

# Inline: (?m)
# (?m)^\w+  ==  with /m flag

# PHP: '/^\w+/m'
# Go: (?m) inline
# .NET: RegexOptions.Multiline

# IMPORTANT: m does NOT change . (use s flag for that)
# m affects ^ and $; s affects .

Dotall s

The s flag (dotAll) makes . match newline too. ES2018+ in JS, re.DOTALL/re.S in Python, Pattern.DOTALL in Java. Confusingly .NET calls it 'Singleline'. Independent of the m (multiline) flag — you can use both. Portable alternative without /s: use [\s\S] or [\d\D] to match any char including newline.

regex
# s flag - dot . matches ALL chars INCLUDING newline

# Without /s: . doesn't match newline
"a\nb".match(/a.b/)    # null
"a\nb".match(/a.b/s)   # ["a\nb"] (with /s)

# Match anything between tags (even across lines)
"<div>a\nb</div>".match(/<div>(.*?)<\/div>/s)
# captures "a\nb"

# JavaScript (ES2018+) - s flag
const re = /<div>[\s\S]*?<\/div>/  # traditional
const re2 = /<div>.*?<\/div>/s        # with /s

# Python: re.DOTALL or re.S
re.search(r"<div>.*?</div>", text, re.DOTALL)

# Inline: (?s)
# (?s).  matches any char including newline

# Java: Pattern.DOTALL
Pattern.compile(".*?", Pattern.DOTALL)

# PHP: '/.*/s'
# Go: (?s) inline
# .NET: RegexOptions.Singleline

# Naming is confusing:
# - JavaScript "s" = "dotAll" (dot matches all)
# - .NET "Singleline" = same (dot matches all)
# - Multiline (m) and Singleline (s) are INDEPENDENT
# - You can use BOTH /ms simultaneously

# Combine m and s
"line1\nline2".replace(/^.*/gms, "X")
# replaces each line entirely with "X"

# Portable alternative (works everywhere): [\s\S] or [\d\D]
/.?[\s\S]*?</tag>/  # portable "any char"

Unicode u

The u flag (JS ES6+) treats patterns as Unicode code points, fixing surrogate-pair issues and enabling \p{...} property escapes. Python 3 is Unicode-by-default (use re.ASCII to restrict). PHP uses /u for UTF-8. Without /u, JS matches UTF-16 code units — broken emoji/CJK handling.

regex
# u flag - treat pattern as Unicode code points (not UTF-16 code units)

# JavaScript (ES6+)
# Without /u: matches UTF-16 code units (broken surrogate pairs)
"𝌆".length             # 2 (surrogate pair)
/^.$/.test("𝌆")         # false (two code units)
/^.$/u.test("𝌆")        # true  (one code point)

# Unicode property escapes (require /u)
/^\p{L}+$/u.test("Hello")   # true (letters)
/\p{Script=Han}/u.test("中文")  # true (CJK characters)
/\p{Emoji}/u.test("👋")       # true

# Case-insensitive Unicode folding
/\w/iu.test("Ω")             # true (Σ/σ/ς equivalence with /iu)

# Python 3: Unicode by default (use re.ASCII for ASCII-only)
re.search(r"\w+", "日本語")  # matches (Unicode default)
re.search(r"\w+", "日本語", re.ASCII)  # ASCII-only

# Python Unicode properties (3.13+ or regex module)
# \p{L} requires regex module or Python 3.13+
import regex  # third-party module
regex.search(r"\p{L}+", "Hello")

# Java: Pattern.UNICODE_CHARACTER_CLASS
Pattern.compile("\\w+", Pattern.UNICODE_CHARACTER_CLASS)
# Java's \w is Unicode by default; flag enables POSIX Unicode classes

# PHP: /u flag for UTF-8 mode
preg_match('/^\p{L}+$/u', 'Hello')  # 1 (Unicode)

# .NET: \p{L} works by default
# Go (RE2): \p{L} works by default

# IMPORTANT: with /u in JS, quantifiers apply to code points
/[😂]{2}/u.test("😂😂")  # true

Sticky y & Extended x

Sticky flag y (JS only) matches at exactly lastIndex — useful for parsers/tokenizers. Extended flag x ignores unescaped whitespace and treats # as comment start, allowing multi-line documented patterns. Python uses re.VERBOSE, PHP/PCRE use /x, inline form is (?x).

regex
# y flag (sticky) - JS only - matches at lastIndex exactly
const re = /abc/y;
re.lastIndex = 0;
re.test("abcabc")  # true, lastIndex now 3
re.test("abcabc")  # true, lastIndex now 6 (matches at exactly 3)
re.test("abcabc")  # false (would need to start at 6, no chars left)

# Useful for tokenizers (must match in sequence)
function tokenize(str) {
  const tokens = [];
  const ws = /\s*/y;
  const num = /\d+/y;
  const op = /[+\-*/]/y;
  let pos = 0;
  while (pos < str.length) {
    ws.lastIndex = pos; let m = ws.exec(str);
    if (m) pos = ws.lastIndex;
    if (pos >= str.length) break;
    num.lastIndex = pos; m = num.exec(str);
    if (m) { tokens.push(["num", m[0]]); pos = num.lastIndex; continue; }
    op.lastIndex = pos; m = op.exec(str);
    if (m) { tokens.push(["op", m[0]]); pos = op.lastIndex; continue; }
    throw new Error("Unexpected at " + pos);
  }
  return tokens;
}

# x flag (extended) - whitespace in pattern ignored, # starts comment
# JavaScript: ES2025+ proposal; widely supported in other engines

# Python (re.VERBOSE)
pattern = r"""
  \d{4}      # year
  -           # separator
  \d{2}      # month
  -           # separator
  \d{2}      # day
"""
re.match(pattern, "2024-01-15", re.VERBOSE)

# PHP: /x flag
preg_match('/
  \d{4}    # year
  -
  \d{2}    # month
/x', '2024-01')

# Inline: (?x)
# (?x)\d{4}-\d{2}  # comment allowed

# To match literal whitespace in /x mode, escape: \  or use [ ]
# \ \t\n all still work; just unescaped space/tab are ignored
10

Common Patterns

Email

Email regex is notoriously hard — the full RFC 5322 regex is huge. Most production code uses a simplified pattern that catches common invalid cases. The only true validation is sending an email with a confirmation link. Reject clearly bad formats but be lenient with edge cases.

regex
# Simplified email (good for most uses)
^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}$

# More strict
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

# RFC 5322 (simplified - the actual RFC is much more complex)
^(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+
   (?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*)
  @
  (?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+
  [a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$

# JavaScript
const emailRe = /^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}$/;
emailRe.test("[email protected]")      # true
emailRe.test("[email protected]")  # true

# Python
import re
re.match(r"^[\w.+-]+@[\w.-]+\.[a-z]{2,}$", "[email protected]", re.I)

# Practical note: the only true validation is sending an email.
# Reject clearly invalid formats, but accept edge cases.

# Common TLD length: 2-24 chars (newer TLDs can be long)
^[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,24}$

URL

URL regex can range from simple (https?://...) to RFC-compliant complex. For production, prefer the built-in URL class (JS) or urllib.parse (Python). When using regex, capture scheme/host/port/path/query/fragment separately for full parsing. Domain regex: labels up to 63 chars, TLD 2+ letters.

regex
# Basic URL pattern
^https?://[\w.-]+(?:\.[a-z]{2,})?(:\d+)?(?:/\S*)?$

# More complete URL parser
^(?:(\w+)://)?              # 1: scheme
([^/:?#]+)                  # 2: host
(?::(\d+))?                 # 3: port
([^?#]*)                    # 4: path
(?:\?([^#]*))?              # 5: query
(?:#(.*))?                  # 6: fragment
$

# JavaScript URL parsing
const urlRe = /^(https?):\/\/([^/:?#]+)(?::(\d+))?
              ([^?#]*)(?:\?([^#]*))?(?:#(.*))?$/ix;
const m = "https://example.com:8080/path?q=1#top".match(urlRe);
// m[1]="https", m[2]="example.com", m[3]="8080",
// m[4]="/path", m[5]="q=1", m[6]="top"

# Python
import re
url_re = re.compile(r'''
  ^(?P<scheme>https?)://
  (?P<host>[^/:?#]+)
  (?::(?P<port>\d+))?
  (?P<path>[^?#]*)
  (?:\?(?P<query>[^#]*))?
  (?:\#(?P<frag>.*))?
  $''', re.VERBOSE | re.IGNORECASE)

# Match domain only
^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$

# Validate URL using built-in URL class (JS)
try { new URL(input); } catch { /* invalid */ }

Phone Number

Phone formats vary widely by country. US: 10 digits with optional separators/country code. International E.164: + then up to 15 digits. For real validation, normalize first (strip non-digits) then check digit count and prefix. Phone APIs/libraries are more reliable than regex for production.

regex
# US phone: 123-456-7890, (123) 456-7890, 1234567890
^(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$

# Capture components
^(?:\+1[-.\s]?)?\(?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})$

# International (E.164): +[country][number], max 15 digits
^\+[1-9]\d{1,14}$

# China phone: 11 digits starting with 1
^1[3-9]\d{9}$

# UK phone: 0XXXXXXXXXX or +44XXXXXXXXXX
^(?:0|\+44)\d{10}$

# Generic international (loose)
^\+?[\d\s().-]{7,}$

# JavaScript
const phoneRe = /^(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
phoneRe.test("(123) 456-7890")  # true
phoneRe.test("123-456-7890")    # true
phoneRe.test("1234567890")      # true

# Python: extract phone from text
re.findall(r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b", text)

# Format phone: 1234567890 -> (123) 456-7890
"1234567890".replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3")

IP Address

IPv4 regex uses alternation to enforce 0-255 per octet: 25[0-5] for 250-255, 2[0-4]\d for 200-249, 1?\d?\d for 0-199. IPv6 is much more complex (with :: shorthand). For private IP detection: 10.x, 172.16-31.x, 192.168.x. Use ipaddress module (Python) or net (JS) for robust parsing.

regex
# IPv4: four octets 0-255
^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}
 (?:25[0-5]|2[0-4]\d|1?\d?\d)$

# Explanation:
# 25[0-5]      -> 250-255
# 2[0-4]\d     -> 200-249
# 1?\d?\d     -> 0-199 (1? optional leading 1, \d? optional tens)

# Python
import re
ipv4_re = re.compile(
    r'^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$'
)
ipv4_re.match("192.168.1.1")   # match
ipv4_re.match("256.0.0.1")     # None (256 > 255)

# Extract IPv4 from text
re.findall(r'\b(?:\d{1,3}\.){3}\d{1,3}\b', text)

# IPv6 (simplified)
^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$
# (:: shorthand, mixed notation are more complex)

# JavaScript
const ipv4 = /^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$/;
ipv4.test("0.0.0.0")       # true
ipv4.test("255.255.255.255") # true
ipv4.test("256.0.0.0")     # false

# Convert IP to number
"192.168.1.1".split(".").reduce((acc, o) => (acc << 8) + +o, 0) >>> 0

# Private IP ranges
^(?:10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.)

Hex Color

Hex color regex: # followed by 3 or 6 hex digits. Use {3,6} cautiously (it allows 4, 5 too). Better: (?:[0-9a-fA-F]{3}){1,2} enforces exactly 3 or 6. For 8-digit (RGBA) support, use [0-9a-fA-F]{3,4} or {8}. CSS colors may also be hsl(), rgb(), named — use a CSS parser for production.

regex
# Hex color: #RGB or #RRGGBB
^#?(?:[0-9a-fA-F]{3}){1,2}$

# Strict with #
^#(?:[0-9a-fA-F]{3}){1,2}$

# With optional alpha: #RGBA or #RRGGBBAA
^#(?:[0-9a-fA-F]{3,4}){1,2}$

# Capture RGB components
^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$

# JavaScript
const hexRe = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
hexRe.test("#fff")       # true
hexRe.test("#FFAABB")    # true
hexRe.test("#12AB56")    # true
hexRe.test("#GGG")       # false
hexRe.test("fff")        # false (no #)

# Allow without #
const hexLoose = /^#?[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$/

# Extract hex colors from CSS
const css = "color: #fff; background: #123456;";
const colors = css.match(/#[0-9a-fA-F]{3,6}/g);
// ["#fff", "#123456"]

# Convert #RGB to #RRGGBB
"#abc".replace(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i, "#$1$1$2$2$3$3")
// "#aabbcc"

# Python
import re
re.match(r"^#(?:[0-9a-fA-F]{3}){1,2}$", "#abc123")

ZIP / Postal Code

Postal code formats vary by country: US ZIP is 5 digits with optional +4; Canada is letter-digit-letter space digit-letter-digit; UK postcodes are complex. Validate against the country's official format. For multi-country apps, choose a regex per country rather than one mega-pattern.

regex
# US ZIP: 12345 or 12345-6789
^\d{5}(?:-\d{4})?$

# Canada postal: A1A 1A1 (letter digit letter space digit letter digit)
^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$

# UK postcode (simplified)
^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$

# China postal code: 6 digits
^\d{6}$

# Germany: 5 digits
^\d{5}$

# France: 5 digits
^\d{5}$

# Australia: 4 digits
^\d{4}$

# JavaScript
const zipRe = /^\d{5}(?:-\d{4})?$/;
zipRe.test("12345")        # true
zipRe.test("12345-6789")   # true
zipRe.test("1234")         # false

# Canada postal
const caRe = /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/;
caRe.test("M5V 2H1")       # true
caRe.test("m5v2h1")        # true

# Python
import re
re.match(r"^\d{5}(?:-\d{4})?$", "12345-6789")  # match

# Format ZIP: 123456789 -> 12345-6789
"123456789".replace(/(\d{5})(\d{4})/, "$1-$2")
11

Password Validation

Minimum Length

Minimum length is the most important password rule. Modern guidance (NIST) recommends at least 8 chars minimum, encouraging longer passphrases (12-16+). Combine with lookaheads for compound rules. The lookahead (?=.{8,}$) asserts length without consuming, allowing other rules to also check from start.

regex
# At least 8 characters
^.{8,}$

# At least 12 characters (modern recommendation)
^.{12,}$

# Between 8 and 128 characters
^.{8,128}$

# Using lookahead (allows combining with other rules)
^(?=.{8,}$).*$

# JavaScript
const lenRe = /^.{8,}$/;
lenRe.test("short")        # false
lenRe.test("longenough")   # true

# Python
import re
bool(re.match(r"^.{8,}$", "longenough"))  # True

# Allow any character including newlines (use s flag or [\s\S])
# /^.{8,}$/s  matches multi-line password

# Common: 8+ with at least one of each type (lookaheads)
^(?=.{8,}$)             # at least 8 chars
(?=.*[a-z])             # at least one lowercase
(?=.*[A-Z])             # at least one uppercase
(?=.*\d)               # at least one digit
(?=.*[^A-Za-z0-9])      # at least one special char
.*$

Uppercase Requirement

Require uppercase with lookahead (?=.*[A-Z]). For N uppercase letters: (?=(?:.*[A-Z]){N}). For Unicode uppercase (Greek, Cyrillic, etc.), use \p{Lu} with the u flag (JS) or regex module (Python). Without lookahead, just test/count separately for cleaner code.

regex
# Must contain at least one uppercase letter
^(?=.*[A-Z]).*$

# Must contain at least one uppercase, 8+ chars
^(?=.*[A-Z]).{8,}$

# At least two uppercase letters
^(?=(?:.*[A-Z]){2}).*$

# JavaScript
const upperRe = /^(?=.*[A-Z]).{8,}$/;
upperRe.test("password")        # false (no uppercase)
upperRe.test("Password")        # true
upperRe.test("PASSWORD")        # true

# Python
import re
bool(re.match(r"^(?=.*[A-Z]).{8,}$", "Password1"))  # True

# Count uppercase letters
const uppers = (pwd) => (pwd.match(/[A-Z]/g) || []).length;
uppers("PassWord")  # 2

# Allow Unicode uppercase (with /u flag in JS)
const unicodeUpper = /^(?=.*\p{Lu}).+$/u;
unicodeUpper.test("Ωμέγα")  # true (Ω is uppercase Greek)

# Python Unicode uppercase
re.search(r"\p{Lu}", "Ωμέγα", regex.UNICODE)  # needs 'regex' module

# .NET: \p{Lu} for any uppercase letter
# Java: \p{Lu} works with Pattern.UNICODE_CHARACTER_CLASS

# Without lookahead: just count
if (/[A-Z]/.test(password)) { /* has uppercase */ }

Digit Requirement

Require digits with (?=.*\d). For N digits: (?=(?:.*\d){N}). \d matches Unicode digits in default mode — use [0-9] for ASCII-only. Many find separate test() calls clearer than stacking lookaheads: read each rule as a single boolean check.

regex
# Must contain at least one digit
^(?=.*\d).*$

# At least one digit, 8+ chars
^(?=.*\d).{8,}$

# At least three digits
^(?=(?:.*\d){3}).*$

# JavaScript
const digitRe = /^(?=.*\d).{8,}$/;
digitRe.test("password")        # false (no digit)
digitRe.test("password1")       # true
digitRe.test("pass1word2")      # true

# Count digits
const digits = (pwd) => (pwd.match(/\d/g) || []).length;
digits("pass12word")  # 2

# Python
import re
bool(re.match(r"^(?=.*\d).{8,}$", "password1"))  # True

# Unicode digits (e.g., Arabic-Indic digits)
/^.*\p{N}/u.test("password١")  # true (١ is Arabic-Indic 1)

# ASCII-only digits explicitly
^(?=.*[0-9]).*$

# Common password rule: must contain letter AND digit
^(?=.*[A-Za-z])(?=.*\d).{8,}$

# Alphanumeric only (no special chars)
^[A-Za-z0-9]{8,}$

# Without regex
const hasDigit = (s) => /\d/.test(s);
const hasLetter = (s) => /[A-Za-z]/.test(s);
if (hasDigit(pwd) && hasLetter(pwd) && pwd.length >= 8) { /* OK */ }

Special Character

Require special chars with (?=.*[^A-Za-z0-9]). Note: space counts as special — exclude it explicitly with a whitelist if needed. Many sites restrict to a safe allow-list (e.g., [!@#$%^&*]). Always check for whitespace separately if you want to disallow it.

regex
# Must contain at least one special (non-alphanumeric) char
^(?=.*[^A-Za-z0-9]).*$

# Or explicit list of special chars
^(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>/?]).*$

# At least one special, 8+ chars
^(?=.*[^A-Za-z0-9]).{8,}$

# JavaScript
const specialRe = /^(?=.*[^A-Za-z0-9]).{8,}$/;
specialRe.test("password")     # false
specialRe.test("password!")    # true
specialRe.test("pass word")    # true (space is non-alphanumeric!)

# To exclude space from "special":
^(?=.*[!@#$%^&*\-_=+]).*$

# Python
import re
bool(re.match(r"^(?=.*[^A-Za-z0-9]).{8,}$", "Password1!"))  # True

# Count special characters
const specials = (pwd) => (pwd.match(/[^A-Za-z0-9]/g) || []).length;

# Common allow-list for special chars in passwords
const ALLOWED_SPECIAL = /[!@#$%^&*\-_=+?]/;
const hasAllowedSpecial = (pwd) => ALLOWED_SPECIAL.test(pwd);

# Disallow whitespace entirely
^(?=\S*$).{8,}$                # no whitespace, 8+ chars
^(?!.*\s).{8,}$                # equivalent

# Without regex
const hasSpecial = (s) => /[^A-Za-z0-9]/.test(s);
const noWhitespace = (s) => !/\s/.test(s);

Combined Validation

Stack lookaheads at the start for compound rules: each lookahead asserts one condition from the same position. For maintainability, separate test() calls may be clearer than one mega-regex. NIST modern guidance: prioritize length (12+), allow all printable chars, and check against known breached passwords.

regex
# Combined password rule:
# - 8+ chars
# - 1+ lowercase
# - 1+ uppercase
# - 1+ digit
# - 1+ special char
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$

# Stronger: 12+ chars, 2+ digits
^(?=.*[a-z])(?=.*[A-Z])(?=(?:.*\d){2})(?=.*[^A-Za-z0-9]).{12,}$

# JavaScript
const strongPwd = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/;
strongPwd.test("Password1!")   # true
strongPwd.test("password")     # false (no upper, digit, special)
strongPwd.test("PASSWORD1!")   # false (no lower)

# Python
import re
STRONG = re.compile(
    r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$"
)
bool(STRONG.match("Password1!"))  # True

# Readable alternative: separate checks (often clearer)
function validatePassword(pwd) {
  const checks = {
    length: pwd.length >= 8,
    lower: /[a-z]/.test(pwd),
    upper: /[A-Z]/.test(pwd),
    digit: /\d/.test(pwd),
    special: /[^A-Za-z0-9]/.test(pwd),
    noWhitespace: !/\s/.test(pwd),
  };
  return Object.values(checks).every(Boolean) ? checks : null;
}

# Disallow common weak passwords
^(?!password|123456|qwerty|letmein).{8,}$

# NIST guidance: focus on length, allow all char types,
# check against breach databases (haveibeenpwned API)
12

Date Matching

YYYY-MM-DD

Regex can validate format (YYYY-MM-DD with 01-12 month, 01-31 day) but NOT semantic validity (Feb 30, leap years). For real validation, use Date (JS) or datetime (Python). Capture groups let you extract year/month/day for further processing. Use ISO 8601 format for unambiguous dates.

regex
# Basic: 4 digits, dash, 2 digits, dash, 2 digits
^\d{4}-\d{2}-\d{2}$

# With basic validation (month 01-12, day 01-31)
^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$

# Disallow year 0000
^(?!0000)\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$

# JavaScript
const dateRe = /^(?!0000)\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/;
dateRe.test("2024-01-15")   # true
dateRe.test("2024-13-01")   # false (month 13)
dateRe.test("2024-02-31")   # true (regex doesn't know Feb has 28 days)

# Capture components
^(\d{4})-(\d{2})-(\d{2})$
"2024-01-15".match(/^(\d{4})-(\d{2})-(\d{2})$/)
// ["2024-01-15", "2024", "01", "15"]

# Python
import re
m = re.match(r"^(\d{4})-(\d{2})-(\d{2})$", "2024-01-15")
year, month, day = m.groups()

# Use Date for real validation
const d = new Date("2024-01-15");
if (isNaN(d.getTime())) { /* invalid */ }

# Python: datetime for real validation
from datetime import date
try: date(2024, 1, 15)
except ValueError: pass  # invalid

DD/MM/YYYY

DD/MM/YYYY (Europe) vs MM/DD/YYYY (US) look identical — ambiguity is a real problem. Always use ISO 8601 (YYYY-MM-DD) for storage/exchange. Regex captures day/month/year so you can reformat. Real validation (Feb 29, leap years, month lengths) requires a date library.

regex
# DD/MM/YYYY with basic validation
^(?:0[1-9]|[12]\d|3[01])/(?:0[1-9]|1[0-2])/\d{4}$

# Capture day, month, year
^(\d{2})/(\d{2})/(\d{4})$

# With separators: / or - or .
^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$

# JavaScript
const re = /^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/;
const m = "15/01/2024".match(re);
// m[1]="15", m[2]="01", m[3]="2024"

# US format MM/DD/YYYY
^(?:0[1-9]|1[0-2])/(?:0[1-9]|[12]\d|3[01])/\d{4}$

# Allow 1 or 2 digit day/month
^(\d{1,2})/(\d{1,2})/(\d{4})$

# Capture and reorder: DD/MM/YYYY -> YYYY-MM-DD
"15/01/2024".replace(/^(\d{2})\/(\d{2})\/(\d{4})$/, "$3-$2-$1")
// "2024-01-15"

# Python
import re
re.sub(r"^(\d{2})/(\d{2})/(\d{4})$", r"\3-\2-\1", "15/01/2024")
# "2024-01-15"

# Detect format ambiguity: 01/02/2024 - is it Jan 2 or Feb 1?
# Use ISO 8601 (YYYY-MM-DD) for unambiguous storage

Time Format

Time regex: HH 00-23 (or 01-12 for 12-hour), MM/SS 00-59. Use alternation ([01]\d|2[0-3]) for hours. Always use ISO 8601 (HH:MM:SS with optional timezone) for storage. For real time arithmetic, use Date/datetime — regex only validates format.

regex
# 24-hour: HH:MM
^([01]\d|2[0-3]):[0-5]\d$

# 24-hour: HH:MM:SS
^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$

# 12-hour with AM/PM
^(0?[1-9]|1[0-2]):[0-5]\d(?:\s?[AP]M)?$

# Capture HH, MM, SS
^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$

# With milliseconds
^([01]\d|2[0-3]):[0-5]\d:[0-5]\d\.\d{3}$

# JavaScript
const time24 = /^([01]\d|2[0-3]):[0-5]\d$/;
time24.test("23:59")  # true
time24.test("24:00")  # false
time24.test("12:30")  # true

const time12 = /^(0?[1-9]|1[0-2]):[0-5]\d\s?[AP]M$/i;
time12.test("1:30 PM")  # true
time12.test("12:00am")  # true

# Python
import re
re.match(r"^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$", "12:30:45")

# ISO 8601 duration: PT1H30M
^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$

# Time zone offset: +HH:MM or -HH:MM
[+-]([01]\d|2[0-3]):?[0-5]\d

# Combined date and time (ISO 8601)
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:?\d{2})?$

ISO 8601 DateTime

ISO 8601 is the international standard for date/time: YYYY-MM-DDTHH:MM:SS with optional fractional seconds and timezone (Z for UTC, or ±HH:MM). For production parsing, use Date (JS) or datetime.fromisoformat (Python 3.7+). Regex is for format validation only.

regex
# ISO 8601 date
^\d{4}-\d{2}-\d{2}$

# ISO 8601 date and time (basic)
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})?$

# With optional milliseconds
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})?$

# JavaScript - use built-in Date parsing
const d = new Date("2024-01-15T10:30:00Z");
if (!isNaN(d.getTime())) { /* valid ISO */ }

# Validate ISO 8601 strictly
const isoRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$/;
isoRe.test("2024-01-15T10:30:00Z")           # true
isoRe.test("2024-01-15T10:30:00.123Z")       # true
isoRe.test("2024-01-15T10:30:00+08:00")      # true
isoRe.test("2024-01-15 10:30:00")            # false (T required)

# Python - use datetime.fromisoformat (3.7+)
from datetime import datetime
try:
    datetime.fromisoformat("2024-01-15T10:30:00")
except ValueError:
    pass  # invalid

# Capture date and time components
^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})?$

# Week date: 2024-W03-1 (year-week-weekday)
^\d{4}-W\d{2}-\d$

# Ordinal date: 2024-015 (year-day of year)
^\d{4}-\d{3}$

Capturing Components

Capture groups let you extract date components for processing. Named groups (Python's (?P<name>...), JS's (?<name>...)) make code much more readable than numeric refs. Always convert month to integer and subtract 1 for JS Date constructor (months are 0-indexed).

regex
# Capture year, month, day from YYYY-MM-DD
^(\d{4})-(\d{2})-(\d{2})$
# Groups: 1=year, 2=month, 3=day

# JavaScript
const m = "2024-01-15".match(/^(\d{4})-(\d{2})-(\d{2})$/);
const [_, year, month, day] = m;
// year="2024", month="01", day="15"

# Named groups (clearer)
const m2 = "2024-01-15".match(
  /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$)/
);
m2.groups.year  // "2024"
m2.groups.month // "01"
m2.groups.day   // "15"

# Python with named groups
import re
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})",
             "2024-01-15")
m.group("year")   # "2024"
m.groupdict()     # {"year": "2024", "month": "01", "day": "15"}

# Reformat date: YYYY-MM-DD -> DD/MM/YYYY
"2024-01-15".replace(
  /^(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})$/,
  "$<d>/$<m>/$<y>"
)
// "15/01/2024"

# Python
re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "2024-01-15")
# "15/01/2024"

# Validate and extract date from text
const text = "Meeting on 2024-01-15 at 10:30";
const [_, y, mo, d] = text.match(/(\d{4})-(\d{2})-(\d{2})/);
// y="2024", mo="01", d="15"

# Convert to Date object
const d = new Date(+y, +mo - 1, +d);  # months are 0-indexed
13

Number Matching

Integers

Integer regex: ^[+-]?\d+$ (sign optional). For thousand separators: ^\d{1,3}(?:,\d{3})*$ enforces groups of three. European format uses dots/spaces as separators. Hex/octal/binary use 0x/0o/0b prefixes (or 0 alone for octal in older languages).

regex
# Positive integers
^\d+$

# Optional sign
^[+-]?\d+$

# Negative only
^-\d+$

# With thousand separators (US/EU: comma)
^[+-]?\d{1,3}(?:,\d{3})*$

# JavaScript
const intRe = /^[+-]?\d+$/;
intRe.test("12345")    # true
intRe.test("-1")       # true
intRe.test("+42")      # true
intRe.test("3.14")     # false

# With commas
const commaInt = /^[+-]?\d{1,3}(?:,\d{3})*$/;
commaInt.test("1,234")        # true
commaInt.test("12,345,678")   # true
commaInt.test("1234")         # false (needs comma)

# Allow comma-less too
^[+-]?(?:\d{1,3}(?:,\d{3})*|\d+)$

# Python
import re
re.match(r"^[+-]?\d+$", "12345")  # match

# Strip commas before parsing
"1,234,567".replace(/,/g, "")  # "1234567"
int("1,234,567".replace(",", ""))  # 1234567

# Hex integer
^0x[0-9a-fA-F]+$

# Octal
^0o?[0-7]+$

# Binary
^0b[01]+$

Decimals

Decimal regex requires care: do you allow leading dot (.5)? trailing dot (1.)? Optional integer part? Common form: ^[+-]?\d+(?:\.\d+)?$ requires integer part, optional decimals. For currency, fix the decimal places (e.g., {2}).

regex
# Basic decimal: digits.digits
^\d+\.\d+$

# Optional integer or fractional part
^\d+\.?\d*$        # 1, 1., 1.5
^\d*\.?\d+$        # .5, 1.5, 1
^\d+\.\d+|\d+$    # integer or decimal

# With optional sign
^[+-]?(?:\d+\.\d+|\d+)$

# JavaScript
const decRe = /^[+-]?(?:\d+\.\d+|\d+)$/;
decRe.test("3.14")     # true
decRe.test("-0.5")     # true
decRe.test("1")        # true
decRe.test(".5")       # false (requires leading digit)

# Allow leading dot
^[+-]?(?:\d+\.?\d*|\.\d+)$

# Fixed decimal places (e.g., 2)
^[+-]?\d+\.\d{2}$

# With thousand separators
^[+-]?\d{1,3}(?:,\d{3})*\.\d+$

# Avoid trailing dot
^[+-]?\d+(?:\.\d+)?$

# Extract decimals from text
"The price is 3.14 dollars".match(/-?\d+\.\d+/)
// ["3.14"]

# Python
import re
re.findall(r"-?\d+\.\d+", "Pi is 3.14, e is 2.72")  # ['3.14', '2.72']

# Strict: 1-6 decimal places
^\d+\.\d{1,6}$

Negative Numbers

Negative number regex: ^-?\d+$ allows optional minus. For strict negatives, ^-\d+$. Be careful extracting from text — hyphens in 'co-operative' can match. Use word boundaries (\b) or require whitespace before the minus to avoid false positives.

regex
# Optional minus sign
^-?\d+$

# Optional + or -
^[+-]?\d+$

# Required minus
^-\d+$

# With decimal
^-?\d+\.\d+$

# JavaScript
const negRe = /^-?\d+$/;
negRe.test("-123")   # true
negRe.test("123")    # true (zero or one minus)
negRe.test("--1")    # false

# Strict negative integer
const strictNeg = /^-\d+$/;
strictNeg.test("-1")    # true
strictNeg.test("0")     # false
strictNeg.test("-0")    # true (technically -0)

# Exclude negative zero
^-(?!0+$)\d+$

# Allow scientific notation with sign
^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$

# Extract signed numbers from text
"Temperature: -5 to +10 degrees".match(/[+-]?\d+/g)
// ["-5", "+10"]

# Python
import re
re.findall(r"-?\d+", "loss: -100, gain: 50")  # ['-100', '50']

# Avoid matching minus as text hyphen
# Use word boundary or whitespace before
\s-?\d+      # minus must follow whitespace
\b-?\d+\b   # word boundary

# Currency with optional negative
^\$?-?\d+(?:\.\d{2})?$  # $-5.00 or -5.00 or $5.00

Scientific Notation

Scientific notation: mantissa (with optional decimal), 'e' or 'E', optional sign, exponent digits. Use parseFloat (JS) or float (Python) for parsing — they handle scientific notation natively. Capture groups let you separately access mantissa and exponent if needed.

regex
# Scientific notation: mantissa e exponent
^[+-]?(?:\d+\.?\d*|\.\d+)[eE][+-]?\d+$

# Examples: 1e10, -2.5E-3, 6.022e23, 1.5E+10

# JavaScript
const sciRe = /^[+-]?(?:\d+\.?\d*|\.\d+)[eE][+-]?\d+$/;
sciRe.test("1e10")        # true
sciRe.test("-2.5E-3")     # true
sciRe.test("6.022e23")    # true
sciRe.test("1.5E+10")     # true
sciRe.test("1e")          # false (no exponent)

# Allow integer or decimal without exponent too
^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$

# Capture mantissa and exponent
^([+-]?(?:\d+\.?\d*|\.\d+))[eE]([+-]?\d+)$

const m = "-2.5E-3".match(/^([+-]?(?:\d+\.?\d*|\.\d+))[eE]([+-]?\d+)$/);
// m[1]="-2.5" (mantissa), m[2]="-3" (exponent)

# Convert to number
parseFloat("6.022e23")  # 6.022e+23
Number("1.5E+10")       # 15000000000

# Python
import re
re.match(r"^[+-]?(?:\d+\.?\d*|\.\d+)[eE][+-]?\d+$", "-2.5E-3")
float("-2.5E-3")  # -0.0025

# Extract scientific numbers from text
re.findall(r"[-+]?\d+\.?\d*[eE][-+]?\d+", "Avogadro: 6.022e23")
# ['6.022e23']

# Engineering notation (exponent multiple of 3)
# Hard to enforce via regex; better to validate after parsing
# Examples: 1.5e3, 2.5e6, 1e-3 (not 1.5e4)

Currency Format

Currency formats vary: US ($1,234.56), EU (1.234,56 €), Japan (¥1,234 no decimals). Use toLocaleString for display formatting. For parsing, strip currency symbol and separators before parseFloat. Regex validates format; use Intl.NumberFormat for production currency formatting.

regex
# US currency: $123.45 or $1,234.56
^\$\d{1,3}(?:,\d{3})*\.\d{2}$

# Optional $ and cents
^\$?\d{1,3}(?:,\d{3})*(?:\.\d{2})?$

# Optional sign for negative
^[+-]?\$?\d{1,3}(?:,\d{3})*(?:\.\d{2})?$

# Allow negative after $: -$5.00 or $-5.00
^[+-]?\$[+-]?\d+(?:\.\d{2})?$

# European format: 1.234,56 €
^\d{1,3}(?:\.\d{3})*,\d{2}\s?€$

# JavaScript
const usd = /^\$\d{1,3}(?:,\d{3})*\.\d{2}$/;
usd.test("$1,234.56")   # true
usd.test("$123.45")     # true
usd.test("$1234.56")    # false (needs comma)
usd.test("$1.234")      # false (needs 2 decimals)

# Loose: just match dollar amount
/\$[\d,.]+/g

# Extract all currency from text
"Total: $1,234.56, tax: $99.99".match(/\$[\d,]+\.\d{2}/g)
// ["$1,234.56", "$99.99"]

# Parse to number
const parseUSD = (s) => parseFloat(s.replace(/[$,]/g, ""));
parseUSD("$1,234.56")  # 1234.56

# Format number as currency
(1234.56).toLocaleString("en-US", { style: "currency", currency: "USD" })
// "$1,234.56"

# Python
import re
re.match(r"^\$\d{1,3}(?:,\d{3})*\.\d{2}$", "$1,234.56")

# Bitcoin: 0.5 BTC, satoshis
^\d+(?:\.\d{1,8})?\s?(?:BTC|sat|sats)$
14

String Replacement

Simple Replace

Replace first vs all differs by language: JS replace() does first-only without /g; Python re.sub does all by default (use count=1 for first); Java replaceFirst for first, replaceAll for all-regex, replace for all-literal. Use regex when matching patterns, literal string replace when replacing fixed text.

regex
# Replace first occurrence

# JavaScript
"hello world".replace(/world/, "there")  # "hello there"
"hello world".replace(/o/, "0")          # "hell0 world" (first only)

# Python
import re
re.sub(r"world", "there", "hello world")  # "hello there"
re.sub(r"o", "0", "hello world", count=1)  # "hell0 world" (first only)

# Java
"hello world".replaceFirst("o", "0")    # "hell0 world"
"hello world".replace("world", "there") # literal, ALL occurrences

# PHP
preg_replace('/world/', 'there', 'hello world', 1)  # limit 1

# Replace literal string (no regex)
"hello.world".replace(".", " ")          # "hello world" (JS, literal)
"hello.world".replaceAll(".", " ")       # "hello world" (JS, literal)
# Python: str.replace is literal
"hello.world".replace(".", " ")          # "hello world"

# Replace using regex with special chars
"1.5".replace(/\./g, ",")               # "1,5" (escape dot)
"1+2=3".replace(/\+/g, "-")             # "1-2=3" (escape +)

# Chain replacements
"  Hello World  ".trim().replace(/\s+/g, "_").toLowerCase()
# "hello_world"

# Python chain
"_".join("  Hello World  ".strip().split()).lower()
# "hello_world"

Replace All

Replace-all behavior: JS needs /g flag with regex or use replaceAll with strings; Python's re.sub and str.replace both replace all by default; Java's replaceAll (regex) and replace (literal) both do all; PHP preg_replace does all. To limit, use count (Python) or $limit (PHP).

regex
# Replace ALL occurrences

# JavaScript: requires /g flag
"a-b-c-d".replace(/-/g, "+")          # "a+b+c+d"
"a-b-c-d".split("-").join("+")        # "a+b+c+d" (alternative)

# JavaScript: replaceAll (ES2021+)
"a-b-c-d".replaceAll("-", "+")        # "a+b+c+d" (string, not regex)
"a-b-c-d".replaceAll(/-/g, "+")       # needs /g flag with regex

# Python: re.sub does all by default
import re
re.sub(r"-", "+", "a-b-c-d")          # "a+b+c+d"

# Python: str.replace also does all
"a-b-c-d".replace("-", "+")           # "a+b+c+d" (literal)

# Java
"a-b-c-d".replaceAll("-", "+")        # regex, all
"a-b-c-d".replace("-", "+")           # literal, all

# PHP
preg_replace('/-/', '+', 'a-b-c-d')   # all

# Go
regexp.MustCompile("-").ReplaceAllString("a-b-c-d", "+")  # "a+b+c+d"

# Count occurrences
const count = (s, re) => (s.match(re) || []).length;
count("a-b-c-d", /-/g)  # 3

# Replace with limit (Python, PHP)
re.sub(r"-", "+", "a-b-c-d", count=2)      # "a+b+c-d"
preg_replace('/-/', '+', 'a-b-c-d', 2)     # "a+b+c-d"

# Global case-insensitive replace
"Hello hello HELLO".replace(/hello/gi, "hi")  # "hi hi hi"

Capture Groups in Replace

Reference captured groups in replacement: JS/Java use $1, $2 (and $& for whole match); Python uses \1, \2 or \g<1> (preferred, avoids ambiguity with \10+); PHP allows $1 or \1. Use $& (JS) / $0 (Java) / \g<0> (Python) for the entire match. Escape literal $ as $$ in JS.

regex
# Use captured groups in replacement

# Swap two words
"hello world".replace(/^(\w+) (\w+)$/, "$2 $1")  # "world hello"

# Reformat date
"2024-01-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1")
# "15/01/2024"

# Wrap matches
"hello".replace(/\w+/g, "[$&]")  # "[hello]"
"hello world".replace(/\w+/g, "[$&]")  # "[hello] [world]"

# Duplicate matches
"abc".replace(/(\w)/g, "$1$1")  # "aabbcc"

# Conditional-like behavior with alternation
"Mr Smith".replace(/(Mr|Ms|Mrs) (\w+)/, "$2, $1.")
# "Smith, Mr."

# Python: \1, \2 or \g<1>, \g<2> (preferred)
import re
re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "2024-01-15")
# "15/01/2024"

re.sub(r"(\w+)", r"[\1]", "hello")  # "[hello]"

# Use named groups
"2024-01-15".replace(
  /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/,
  "$<d>/$<m>/$<y>"
)  # "15/01/2024"

# Python named
re.sub(
  r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})",
  r"\g<d>/\g<m>/\g<y>",
  "2024-01-15"
)

# Reference whole match: $& (JS), $0 (Java), \g<0> (Python)
"abc".replace(/\w/g, "[$&]")  # "[a][b][c]"
re.sub(r"\w", r"[\g<0>]", "abc")  # "[a][b][c]"

# Literal $ in replacement (JS): use $$
"price".replace(/price/, "$$100")  # "$100"

Case Conversion

Case conversion via regex + function replacement: title case (capitalize each word), camelCase↔snake_case↔kebab-case conversions use capture groups plus toUpperCase/toLowerCase. Python uses lambda functions for the same effect. The pattern ([A-Z]) captured before replacement is key for case-style conversions.

regex
# Capitalize first letter of each word (title case)
"hello world".replace(/\b\w/g, c => c.toUpperCase())
# "Hello World"

# Capitalize first letter of string only
"hello world".replace(/^\w/, c => c.toUpperCase())
# "Hello world"

# Lowercase first letter, rest unchanged
"HelloWorld".replace(/^\w/, c => c.toLowerCase())
# "helloWorld"

# camelCase to snake_case
"myVariableName".replace(/([A-Z])/g, "_$1").toLowerCase()
# "my_variable_name"

# snake_case to camelCase
"my_variable_name".replace(/_([a-z])/g, (_, c) => c.toUpperCase())
# "myVariableName"

# kebab-case to camelCase
"my-variable-name".replace(/-([a-z])/g, (_, c) => c.toUpperCase())
# "myVariableName"

# camelCase to kebab-case
"myVariableName".replace(/([A-Z])/g, "-$1").toLowerCase()
# "my-variable-name"

# UPPER_CASE to lowerCase
"MY_VAR_NAME".replace(/_/g, "").toLowerCase()
# "myvarname" (without proper conversion)

# Constant case to camelCase
"MY_VAR_NAME".toLowerCase().replace(/_([a-z])/g, (_, c) => c.toUpperCase())
# "myVarName"

# Sentence case: capitalize first letter of each sentence
"hello. world. foo.".replace(/(^|\.\s+)([a-z])/g, (_, p, c) => p + c.toUpperCase())
# "Hello. World. Foo."

# Python equivalents
import re
re.sub(r"\b\w", lambda m: m.group().upper(), "hello world")
# "Hello World"

re.sub(r"([A-Z])", r"_\1", "myVar").lower()
# "my_var"

Trim & Cleanup

Common cleanup: trim (use built-in .trim() in JS, .strip() in Python — faster), collapse multiple spaces (\s+ -> single space), normalize line endings (CRLF/CR -> LF), strip comments. Use multiline flag (/m) when applying per-line operations like removing trailing whitespace from each line.

regex
# Trim leading and trailing whitespace
"  hi  ".replace(/^\s+|\s+$/g, "")  # "hi"
# Note: JS String.prototype.trim() is preferred: "  hi  ".trim()

# Trim leading only
"  hi  ".replace(/^\s+/, "")  # "hi  "
# Or: "  hi  ".replace(/^\s+/g, "")

# Trim trailing only
"  hi  ".replace(/\s+$/, "")  # "  hi"

# Collapse multiple spaces into one
"a   b    c".replace(/\s+/g, " ")  # "a b c"

# Collapse but preserve newlines
"a   b

  c".replace(/[ \t]+/g, " ").replace(/\n{2,}/g, "\n")
# "a b
c"

# Remove all whitespace
"a b c".replace(/\s/g, "")  # "abc"
"a b c".replace(/\s+/g, "")  # "abc"

# Remove blank lines
"text\n\n\nmore".replace(/^\s*$(?:\n|$)/gm, "")
# "text
more"

# Normalize line endings
"text\r\nmore".replace(/\r\n/g, "\n")  # CRLF -> LF
"text\rmore".replace(/\r/g, "\n")        # CR -> LF

# Remove trailing whitespace from each line
"text  \nmore ".replace(/\s+$/gm, "")
# "text
more"

# Strip comments (/* */ and //)
code.replace(/\/\*[\s\S]*?\*\//g, "")  # block comments
code.replace(/\/\/.*$/gm, "")              # line comments

# Python equivalents
import re
re.sub(r"^\s+|\s+$", "", "  hi  ")  # "hi"
re.sub(r"\s+", " ", "a   b    c")    # "a b c"
re.sub(r"\s+$", "", "a   ", flags=re.M)  # trim trailing per line

Remove HTML Tags

Strip HTML tags with /<[^>]+>/g (naive but works for simple cases). Remove <script>/<style> with their content using lazy [\s\S]*?. For real HTML, use a parser (DOM in JS, BeautifulSoup in Python) — regex fails on nested tags, attributes with >, and malformed HTML. Decode entities with the DOM or html.unescape (Python).

regex
# Naive: remove anything between < and >
"<p>Hello</p>".replace(/<[^>]+>/g, "")  # "Hello"

# Strip all tags but keep content
"<div><p>Hello <b>world</b></p></div>".replace(/<[^>]+>/g, "")
# "Hello world"

# Remove specific tag
"<div>keep</div>".replace(/<\/?div>/g, "")  # "keep"

# Remove script and style blocks (with content)
html.replace(/<script[\s\S]*?<\/script>/gi, "")
html.replace(/<style[\s\S]*?<\/style>/gi, "")

# Decode basic HTML entities
"AT&amp;T".replace(/&amp;/g, "&")
"&lt;tag&gt;".replace(/&lt;/g, "<").replace(/&gt;/g, ">")
"&nbsp;".replace(/&nbsp;/g, " ")
"&quot;".replace(/&quot;/g, '\"')

# Decode all entities via DOM (JS)
function decodeEntities(s) {
  const t = document.createElement("textarea");
  t.innerHTML = s;
  return t.value;
}

# Remove attributes from tags
'<a href="x" class="y">link</a>'.replace(/(<\w+)[^>]*>/g, "$1>")
# "<a>link</a>" (but the </a> stays; need to handle closing too)

# Self-closing tags
html.replace(/<\w+[^>]*/>/g, "")

# Strip HTML comments
html.replace(/<!--[\s\S]*?-->/g, "")

# Python
import re
re.sub(r"<[^>]+>", "", "<p>Hello</p>")  # "Hello"
re.sub(r"<script[\s\S]*?</script>", "", html, flags=re.I)

# WARNING: regex is fragile for HTML parsing.
# Use DOMParser (JS) or BeautifulSoup (Python) for real HTML.
15

Extraction & Splitting

Extract First Match

Extract first match: JS String.match() returns array (or null), Python re.search returns Match object (or None), Java Matcher.find() advances to next match, PHP preg_match fills array with groups. Use search (Python) when pattern may be anywhere — match anchors at start.

regex
# Extract first match

# JavaScript
const m = "price: $42.50, tax: $3.00".match(/\$(\d+\.\d{2})/);
// m[0] = "$42.50", m[1] = "42.50"
if (m) console.log(m[1]);  // "42.50"

# Python
import re
m = re.search(r"\$(\d+\.\d{2})", "price: $42.50, tax: $3.00")
if m:
    print(m.group(1))  # "42.50"

# Java
Matcher m = Pattern.compile("\\$(\\d+\\.\\d{2})")
                   .matcher("price: $42.50");
if (m.find()) { String price = m.group(1); }

# PHP
if (preg_match('/\$(\d+\.\d{2})/', 'price: $42.50', $m)) {
    echo $m[1];  // "42.50"
}

# Extract with named groups
const m2 = "2024-01-15".match(
  /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
);
if (m2) console.log(m2.groups.year);  // "2024"

# Test if pattern exists (no extraction needed)
const has = /\d{4}-\d{2}-\d{2}/.test("date: 2024-01-15");  // true

# Python: search vs match vs fullmatch
re.search(r"abc", "xabcx")   # finds anywhere
re.match(r"abc", "abcx")     # anchored at start
re.fullmatch(r"abc", "abc")  # entire string

Extract All Matches

Extract all matches: JS match() with /g returns flat array of matches (no group details); matchAll (ES2020+) returns iterator with full info. Python's re.findall returns list of groups (or whole match if no groups). Use re.finditer for lazy iteration over large text.

regex
# Extract ALL matches

# JavaScript: with /g flag, match() returns array of matches (no groups)
"1a2b3c".match(/\d/g)        # ["1", "2", "3"]
"a1 b2 c3".match(/\d+/g)     # ["1", "2", "3"]

# matchAll (ES2020+) returns iterator with full match info
const matches = [..."2024-01-15 2025-02-20".matchAll(/(\d{4})-(\d{2})-(\d{2})/g)];
// matches[0][0]="2024-01-15", matches[0][1]="2024", ...

# Python: re.findall returns list of group(s)
import re
re.findall(r"\d+", "a1 b2 c3")            # ["1", "2", "3"]
re.findall(r"(\d+)-(\d+)", "12-34 56-78")  # [("12","34"), ("56","78")]
# Note: if groups present, returns tuples of groups, not whole match
# Use (?:...) or non-capturing to get whole match
re.findall(r"\d+-\d+", "12-34 56-78")    # ["12-34", "56-78"]

# re.finditer returns match objects (lazy iterator)
for m in re.finditer(r"\d+", "a1 b2 c3"):
    print(m.group(), m.start(), m.end())

# Java: Matcher.find() in loop
Matcher m = Pattern.compile("\\d+").matcher("a1 b2 c3");
while (m.find()) { System.out.println(m.group()); }

# PHP: preg_match_all fills multidimensional array
preg_match_all('/\d+/', 'a1 b2 c3', $m, PREG_SET_ORDER);
// $m[0][0] = "1", $m[1][0] = "2", ...

# Count matches
(len("a1 b2 c3".match(/\d/g) || []))  # 3 (JS)
len(re.findall(r"\d", "a1 b2 c3"))     # 3 (Python)

Split String

Split strings by regex: JS String.split(regex), Python re.split, Java String.split, PHP preg_split, Go regexp.Split. Capture groups in the pattern keep the delimiters in the result array. Use limit/maxsplit to cap the number of splits. Whitespace split: /\s+/ handles multiple spaces, tabs, newlines.

regex
# Split string by pattern

# JavaScript
"a,b,c".split(",")            # ["a", "b", "c"]
"a, b , c".split(/\s*,\s*/)  # ["a", "b", "c"] (trim around comma)
"1-2-3".split("-")            # ["1", "2", "3"]

# Split with capture group keeps the delimiter
"a1b2c3".split(/(\d)/)       # ["a", "1", "b", "2", "c", "3", ""]

# Limit splits
"a,b,c,d".split(",", 2)       # ["a", "b"] (rest in last element)
"a,b,c,d".split(/,/, 2)       # ["a", "b"]

# Python
import re
re.split(r"\s*,\s*", "a, b , c")  # ["a", "b", "c"]
re.split(r"(\d)", "a1b2c3")         # ["a", "1", "b", "2", "c", "3", ""]
re.split(r",", "a,b,c", maxsplit=1)  # ["a", "b,c"]

# Java
"a,b,c".split(",")            # ["a", "b", "c"]
"a, b , c".split("\\s*,\\s*")  # ["a", "b", "c"]
"a,b,c,d".split(",", 2)       # ["a", "b,c,d"]

# PHP
preg_split('/\s*,\s*/', 'a, b , c')  # ["a", "b", "c"]
preg_split('/(\d)/', 'a1b2c3', -1, PREG_SPLIT_DELIM_CAPTURE)

# Go
regexp.MustCompile("\\s*,\\s*").Split("a, b , c", -1)

# Split on whitespace (any)
"a   b\tc\nd".split(/\s+/)  # ["a", "b", "c", "d"]

# Split lines
"line1\nline2\r\nline3".split(/\r\n|\r|\n/)  # ["line1", "line2", "line3"]

Extract Key-Value Pairs

Extract key=value pairs with pattern like (\w+)\s*[=:]\s*([^,;\n]+). Object.fromEntries + matchAll (JS) or dict + findall (Python) build a clean object. For real URL query strings and HTTP headers, prefer built-in URL/URLSearchParams (JS) or urllib.parse (Python) — they handle URL encoding correctly.

regex
# Extract key=value pairs

# Match key=value (or key: value)
(\w+)\s*[=:]\s*([^,;\n]+)

# JavaScript
const text = "name=Alice, age=30; city: NYC";
const pairs = [...text.matchAll(/(\w+)\s*[=:]\s*([^,;\n]+)/g)];
const obj = Object.fromEntries(pairs.map(m => [m[1], m[2].trim()]));
// { name: "Alice", age: "30", city: "NYC" }

# Python
import re
text = "name=Alice, age=30; city: NYC"
pairs = re.findall(r"(\w+)\s*[=:]\s*([^,;\n]+)", text)
dict(pairs)  # {"name": "Alice", "age": "30", "city": "NYC"}

# URL query string parsing
"?a=1&b=2&c=3".match(/[?&]([^=]+)=([^&]+)/g)
// ["?a=1", "&b=2", "&c=3"]

# Parse query string into object
const qs = "a=1&b=2&c=hello%20world";
const params = Object.fromEntries(
  [...qs.matchAll(/([^=&]+)=([^&]*)/g)]
    .map(m => [m[1], decodeURIComponent(m[2])])
);
// { a: "1", b: "2", c: "hello world" }

# Python equivalent
from urllib.parse import parse_qs
parse_qs("a=1&b=2&c=hello%20world")
# {"a": ["1"], "b": ["2"], "c": ["hello world"]}

# HTTP header parsing
const headers = "Content-Type: text/html\nAccept: */*";
const headerObj = Object.fromEntries(
  [...headers.matchAll(/^(\w[\w-]*):\s*(.+)$/gm)]
    .map(m => [m[1], m[2]])
);
// { "Content-Type": "text/html", "Accept": "*/*" }

Tokenization

Tokenization splits text into meaningful units: words (\w+), numbers, strings, operators, punctuation. Use alternation with capture groups for tokenizer patterns. Python's re.finditer gives match objects with positions. For real NLP tokenization, use libraries like NLTK or spaCy — they handle edge cases regex can't.

regex
# Tokenize: split text into tokens (words, numbers, punctuation)

# Simple word tokenization
\w+              # sequences of word chars
"Hello, world! 42".match(/\w+/g)  # ["Hello", "world", "42"]

# Tokenize keeping punctuation
/[A-Za-z0-9]+|[^\sA-Za-z0-9]/g
"Hello, world!".match(/[A-Za-z0-9]+|[^\sA-Za-z0-9]/g)
# ["Hello", ",", "world", "!"]

# Number tokens
/\d+\.?\d*|\.[0-9]+/
"Price 3.14 and .5".match(/\d+\.?\d*|\.[0-9]+/g)  # ["3.14", ".5"]

# String literals
/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/

# Programming language tokens (simplified)
const tokens = source.matchAll(
  /(?<comment>\/\/.*$|\/\*[\s\S]*?\*\/)
   |(?<string>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')
   |(?<number>\d+\.?\d*)
   |(?<ident>[A-Za-z_]\w*)
   |(?<op>[+\-*/=<>!&|]+)
   |(?<punct>[(){}[\];,.])/gx
);

# Split into words preserving position
import re
for m in re.finditer(r"\w+", "Hello world foo"):
    print(m.group(), m.start(), m.end())

# Sentence tokenization (simplified)
text.split(/(?<=[.!?])\s+/)
# Splits on whitespace after . ! or ?

# Whitespace tokenization (preserves tokens)
text.split(/(\s+)/)  # keeps whitespace as separate elements
16

Greedy vs Lazy

Greedy Default

Greedy quantifiers match as much as possible, then backtrack to let the rest of the pattern succeed. This often causes unexpected matches like HTML: <.+> matches an entire <b>...</b><i>...</i> as one. Use lazy quantifiers or specific character classes to avoid this.

regex
# Quantifiers are GREEDY by default - match as much as possible
# * + ? {n,m} all greedy

# Greedy: .* takes everything, then backtracks
"<a> <b> <c>".match(/<.*>/)   # ["<a> <b> <c>"] (whole thing!)

# Greedy backtracking example
"12345".match(/\d+\d/)   # \d+ takes "1234", last \d takes "5"

# Greedy with anchored end - takes everything then backtracks to find end
"hello world end".match(/^.*(end).*$/)  # captures "end"

# Common greedy patterns
.*            # entire line (zero or more)
.+            # entire line (one or more)
\d+           # all consecutive digits
\w+           # all consecutive word chars
\s+           # all consecutive whitespace

# HTML gotcha: greedy matches too much
"<b>bold</b> and <i>italic</i>".match(/<.+>/)
# ["<b>bold</b> and <i>italic</i>"] - WRONG, matches across tags!

# Greedy on alternation
"cat or dog".match(/cat|cat dog/)  # ["cat"] - first alternative wins

# Email greedy
"a@b@c".match(/.+@.+/)  # ["a@b@c"] - greedy .+ takes "a@b"

Lazy Quantifiers

Lazy quantifiers (*? +? ??) match as little as possible. Use them for finding the shortest match between delimiters: <.*?> for HTML tags, ".*?" for quoted strings. For simple quoted strings, '[^']*' is usually faster and clearer than '.*?'.

regex
# Add ? after a quantifier to make it LAZY (non-greedy)
# Lazy: match as LITTLE as possible, expand only if needed

# Lazy quantifiers
*?     # lazy zero or more
+?     # lazy one or more
??     # lazy zero or one
{n,m}? # lazy range

# Lazy: stops at FIRST closing tag
"<a> <b> <c>".match(/<.*?>/)   # ["<a>"] - just the first tag
"<b>bold</b> <i>italic</i>".match(/<.+?>/)
# ["<b>"] - just the first opening tag

# Find quoted strings
'"a" and "b"'.match(/".*?"/g)   # ['"a"', '"b"']
'"a" and "b"'.match(/".*"/g)    # ['"a" and "b"'] (greedy wrong!)

# Capture content between tags
"<div>hello</div>".match(/<div>(.*?)<\/div>/)  # captures "hello"

# Lazy vs negated class (often faster alternative)
# These are equivalent but negated class is usually faster:
'"hello"'.match(/"[^"]*"/)     # uses negated class - preferred
'"hello"'.match(/".*?"/)       # uses lazy - works but slower

# Multiple lazy quantifiers
'(.*?)(.*?)(.*?)'  # all lazy, but each expands as needed

# When greedy fails, lazy often helps
# Match XML comments <!-- ... -->
text.match(/<!--[\s\S]*?-->/g)  # lazy: stops at first -->

Common Lazy Patterns

Common lazy patterns: tag content (<tag>(.*?)</tag>), quoted strings ('[^']*'), bracketed content, comments. For delimited content, prefer negated character classes ([^']*) over lazy quantifiers (.*?) — they're clearer and often faster. Lazy really shines when the delimiter is multi-character.

regex
# Most common lazy patterns

# Content between tags (HTML/XML)
<([a-z][a-z0-9]*)\b[^>]*>(.*?)<\/\1>
# captures tag name and inner content (lazy)

# Quoted strings
"([^"]*)"        # double-quoted (preferred over ".*?")
'([^']*)'        # single-quoted

# Bracketed content
\[([^\]]*)\]   # content inside [ ]
\(([^()]*)\)   # content inside ( )
\{([^{}]*)\}   # content inside { }

# Comments
/\*[\s\S]*?\*/   # C-style block comment
<!--.*?-->        # HTML comment (with /s for multiline)

# URL in text
https?://\S+?["'\s<>]   # URL until delimiter

# Path components
/([^/]+)         # one path segment

# Code block in markdown
\`\`\`([\s\S]*?)\`\`\`   # fenced code block

# Lazy with anchors for validation
^.*?\bword\b.*$  # line containing "word" (lazy doesn't matter here)

# Variable assignment
(\w+)\s*=\s*("[^"]*"|'[^']*'|\d+)

When to Use Each

Choose greedy when you want the longest match (until last delimiter) or the end is unique. Choose lazy when multiple delimiters exist or you want each match separately. Best of all: use specific character classes ([^"/]*) to avoid the issue entirely — they're usually clearer and faster than lazy quantifiers.

regex
# Use GREEDY when:
# - You want the longest possible match
# - The end is uniquely identified
# - You want to capture until the LAST occurrence

# Capture everything to the last occurrence of X
^.*X         # greedy: matches up to the LAST X
"everything;until;last;semicolon".match(/^.*;/)  # "everything;until;last;"

# Use LAZY when:
# - You want the shortest match
# - Multiple delimiters exist
# - You want to match each occurrence separately

# Match each quoted string
'"a","b","c"'.match(/"[^"]*"/g)   # ['"a"', '"b"', '"c"'] - non-lazy, negated
'"a","b","c"'.match(/".*?"/g)     # same result with lazy

# When neither works well: use specific character classes
# Match content of <div> (allowing nested non-div tags)
<div\b[^>]*>((?:(?!<div\b)[\s\S])*)<\/div>

# Practical: extract first URL from text
const text = 'visit https://example.com or https://other.com';
const m = text.match(/https?:\/\/[^\s<>"]+/i);
// m[0] = "https://example.com" (greedy on char class, stops at space)

# Use anchored patterns to avoid greediness issues
^.*?word.*$    # lazy doesn't help here - anchored greedy works fine

# Performance: greedy can be FASTER when no backtracking needed
\d+           # greedy but no backtrack issue (concrete chars)
.*?word        # lazy to avoid grabbing too much

Catastrophic Backtracking

Catastrophic backtracking: nested quantifiers with overlapping alternatives (like (a+)+) cause exponential time on non-matching inputs. Fix with atomic groups (?>...), possessive quantifiers (a++), refactoring to avoid overlaps, timeouts (Python 3.11+), or using RE2 (Go's engine) which is linear-time but doesn't support backreferences.

regex
# Catastrophic backtracking: exponential time on certain inputs
# Caused by nested quantifiers with overlapping alternatives

# DANGEROUS: (a+)+ on "aaaaaaaaaaaaaaaaaaaaaaa!"
(a+)+         # nested quantifier - exponential backtracking on no-match

# DANGEROUS: overlapping alternatives
(a|a)*b       # tries many combinations before failing

# DANGEROUS: classic email regex
([a-zA-Z0-9._%+-]+@([a-zA-Z0-9.-]+\.)+[a-zA-Z]{2,})+
# nested + with alternation can blow up

# How to detect: test with long inputs that DON'T match
# If it hangs on "aaaaaaa...!" - you have it

# Fixes:
# 1. Use atomic groups (PCRE, Java, .NET - NOT JS)
(?>a+)+       # atomic group - no backtracking into group

# 2. Use possessive quantifiers (Java, PCRE - NOT JS)
a++           # possessive - no backtracking

# 3. Refactor: avoid overlapping alternatives
# Bad:  (a|a)*b
# Good: a*b

# 4. Be specific with character classes
# Bad:  (.*)*
# Good: [a-z]*  (or whatever specific chars)

# 5. Use unrolling the loop technique
# Replace (?:a*b*)+ with a*(?:ba*b)*  (for example)

# 6. Set a timeout (Python 3.11+)
import re
re.compile(r"(a+)+").match("a" * 30 + "b", timeout=1.0)

# 7. Use RE2 (linear-time engine) in Go, or Google's re2 library
# RE2 doesn't support backreferences but is immune to this issue

# Test your regex at regex101.com - it warns about catastrophic patterns
17

Backreferences

Numbered Backreferences

Backreferences (\1, \2) match the same text captured earlier. Classic uses: repeated words (\b(\w+)\s+\1\b), HTML tag pairs (<([a-z]+)>...</\1>), paired quotes, consistent date separators. The backreference must match exactly what the group captured — not just match the pattern again.

regex
# \1, \2, ... refer to a PREVIOUSLY captured group's match
# The backreference must match the SAME text the group captured

# Find repeated word
\b(\w+)\s+\1\b
# Matches: "hello hello", "the the", "is is"

# Match HTML/XML tag pairs
<([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1>
# <div>...</div>, <p>...</p>, <span class="x">...</span>

# Match paired quotes (single or double)
(["\'])([^\1]*?)\1
# "hello" or 'hello' - same opening and closing quote

# Date with consistent separator
(\d{4})([-/])(\d{2})\2(\d{2})
# 2024-01-15 OK (same -)
# 2024-01/15 REJECTED (different separators)

# Palindrome-ish: abba
(\w)(\w)\2\1

# JavaScript
"hello hello world".match(/\b(\w+)\s+\1\b/)  # ["hello hello", "hello"]
"<div>x</div>".match(/<([a-z]+)>([\s\S]*?)<\/\1>/)  # ["<div>x</div>", "div", "x"]

# Python
import re
re.search(r"\b(\w+)\s+\1\b", "hello hello")  # match
re.search(r"<([a-z]+)>[\s\S]*?</\1>", "<b>x</b>")  # match

# Backreferences in replacement (different from in-pattern backrefs)
"hello".replace(/(\w)/g, "$1$1")  # "hheelllloo"

Named Backreferences

Named backreferences make patterns self-documenting. JS/.NET/Java use \k<name>; Python/PCRE use (?P=name). Combine with named groups (?<name>...) for tag pair matching, paired delimiters, and repeated word detection. Named backrefs in replacement use $<name> (JS) or \g<name> (Python).

regex
# Named capture group + named backreference

# JavaScript: (?<name>...) then \k<name>
(?<word>\w+)\s+\k<word>
(?<tag>[a-z]+)>[\s\S]*?</\k<tag>>

# Python: (?P<name>...) then (?P=name)
(?P<word>\w+)\s+(?P=word)
(?P<tag>[a-z]+)>[\s\S]*?</(?P=tag)>

# .NET: (?<name>...) then \k<name> (same as JS)
# Java: (?<name>...) then \k<name>
# PHP/PCRE: (?P<name>...) then (?P=name) or \k<name>

# JavaScript example
const re = /\b(?<word>\w+)\s+\k<word>\b/;
"the the cat".match(re)  # ["the the", "the"]

# Python example
import re
re.search(r"(?P<word>\w+)\s+(?P=word)", "hello hello")  # match

# Practical: match HTML with named groups
const html = '<div class="x"><span>hi</span></div>';
const re2 = /<(?<tag>[a-z][a-z0-9]*)\b[^>]*>(?<content>[\s\S]*?)<\/\k<tag>>/;
const m = html.match(re2);
// m.groups.tag = "div", m.groups.content = '<span>hi</span>'

# Quote matching with named group
const quoteRe = /(?<q>["\'])[^\k<q>]*\k<q>/g;
'text "hello" and \'world\''.match(quoteRe)  # ['"hello"', "\'world\'"]

# Named backref in replacement (JS)
"hello hello".replace(/(?<w>\w+)\s+\k<w>/, "$<w>")  # "hello"

Repeated Words

Repeated word detection: \b(\w+)\s+\1\b finds doubled words like 'the the'. Use /i for case-insensitive. Fix them by replacing with $1 (JS) or \1 (Python). Variations: triple repeats with (\s+\1){2}, only common words, exclude valid doubles like 'had had' with negative lookahead.

regex
# Find repeated (doubled) words: "the the", "is is"
\b(\w+)\s+\1\b

# Case-insensitive
/\b(\w+)\s+\1\b/i

# JavaScript
const dupRe = /\b(\w+)\s+\1\b/gi;
"I went to the the store and and bought milk".match(dupRe)
# ["the the", "and and"]

# Python
import re
re.findall(r"\b(\w+)\s+\1\b", "the the cat", re.IGNORECASE)
# ["the"]

# Fix doubled words (replace with single)
"I went to the the store".replace(/\b(\w+)\s+\1\b/gi, "$1")
# "I went to the store"

# Python
re.sub(r"\b(\w+)\s+\1\b", r"\1", "the the cat", flags=re.I)
# "the cat"

# Find triple repeats
\b(\w+)(\s+\1){2}\b  # "blah blah blah"

# Find any consecutive repeats
\b(\w+)(?:\s+\1)+\b  # "blah blah blah blah"

# Limit to specific words
\b(the|and|or|is)\s+\1\b  # only common stop words

# Find non-trivial repeats (3+ letters)
\b(\w{3,})\s+\1\b  # excludes short words like "is is"

# Skip common valid doubles (very rare in English)
# "had had", "that that" - use negative lookahead to exclude
\b(?!had|that)(\w+)\s+\1\b

HTML Tag Matching

Match HTML tag pairs with <([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1> — captures tag name and uses backreference to ensure closing tag matches. WARNING: regex CANNOT handle nested same-name tags (like <div><div>x</div></div>) — use a real parser (DOM in JS, BeautifulSoup in Python) for nested HTML.

regex
# Match opening and closing tag with backreference
<([a-z][a-z0-9]*)\b[^>]*>[\s\S]*?</\1>
# <div>...</div>, <span class="x">...</span>

# JavaScript
const html = '<div class="x"><p>hello</p></div>';
const re = /<([a-z][a-z0-9]*)\b[^>]*>([\s\S]*?)<\/\1>/gi;
const m = html.match(re);
// ["<div class="x"><p>hello</p></div>"]

# Iterate over all top-level tags
const re2 = /<([a-z][a-z0-9]*)\b[^>]*>([\s\S]*?)<\/\1>/gi;
let m2;
while ((m2 = re2.exec(html)) !== null) {
  console.log(m2[1], m2[2]);
}

# Python
import re
re.findall(r"<([a-z][a-z0-9]*)\b[^>]*>([\s\S]*?)</\1>",
           html, re.IGNORECASE)
# [("div", "<p>hello</p>")]

# Self-closing tags
<([a-z][a-z0-9]*)\b[^>]*/>

# Both opening and self-closing
<([a-z][a-z0-9]*)\b[^>]*(?:/>|>[\s\S]*?</\1>)

# Specific tag
<div\b[^>]*>([\s\S]*?)</div>

# WARNING: backreferences can't handle nested same-name tags
# <div><div>x</div></div> - regex can't match outer properly
# Use a parser for nested HTML

# Attribute extraction
<(?<tag>[a-z]+)(?<attrs>[^>]*)>(?<content>[\s\S]*?)</\k<tag>>

Quote Matching

Match quoted strings with consistent delimiters using (['\"])([^\1]*?)\1 — captures the opening quote and requires the same char to close. For strings with escaped quotes (like "hello \"world\""), use the more complex (['\"])(?:\\.|(?!\1)[^\\])*\1. Naive patterns break on escaped quotes.

regex
# Match a quoted string with CONSISTENT quote char
(["\'])([^\1]*?)\1
# "hello" or 'hello' - opening quote remembered, closing must match

# JavaScript
const str = 'She said "hello" and he said \'hi\'';
const re = /(["\'])([^"\']*?)\1/g;
str.match(re)  # ['"hello"', "\'hi\'"]

# With named group (clearer)
const re2 = /(?<q>["\'])(?<content>[^\k<q>]*?)\k<q>/g;

# Handle escaped quotes inside
# Allow \" inside double quotes, \' inside single
(["\'])(?:\\.|(?!\1)[^\\])*\1
# "hello \"world\"" matches correctly

# Python
import re
re.findall(r'(["\'])([^\1]*?)\1', 'She said "hello"')
# [('"', 'hello')]

# Practical: extract all quoted strings
text.match(/(["\'])(?:(?!\1).)*\1/g)

# Triple-quoted strings (Python-like)
(?<="""|''')[\s\S]*?\1

# Backtick strings (JS template literals)
`(?:[^\`\\]|\\.)*`

# Replace quoted content
'"hello world"'.replace(/"([^"]*)"/, "[$1]")  # "[hello world]"

# WARNING: this naive pattern fails on escaped quotes:
# "hello \"world\"" - the \" ends the match early
# Use the escape-aware version above for production
18

Unicode Regex

Unicode Mode

Unicode mode is essential for non-ASCII text: JS uses /u flag (ES6+), Python 3 is Unicode by default (use re.ASCII to restrict), PHP uses /u, Java uses Pattern.UNICODE_CHARACTER_CLASS. Without /u in JS, surrogate pairs (emoji, CJK) are mishandled — quantifiers and anchors treat them as two code units instead of one.

regex
# JavaScript: /u flag for Unicode mode (ES6+)
/^.$/u.test("𝌆")    # true (one code point)
/^.$/.test("𝌆")     # false (two UTF-16 code units without /u)

# Without /u: surrogate pairs broken
"𝌆".length          # 2 (surrogate pair)
/[\uD834\uDF06]/u.test("𝌆")  # true (with /u, treats as one code point)

# Python 3: Unicode is default
import re
re.search(r"\w+", "日本語")  # matches (Unicode by default)
# re.ASCII or re.A for ASCII-only
re.search(r"\w+", "日本語", re.ASCII)  # ASCII-only, no match

# PHP: /u flag for UTF-8 mode
preg_match('/^.$/u', '𝌆')  # 1 (match, treats as one code point)

# Java: Pattern.UNICODE_CHARACTER_CLASS
Pattern p = Pattern.compile("\\w+", Pattern.UNICODE_CHARACTER_CLASS);

# .NET: Unicode by default
# Go (RE2): Unicode by default

# IMPORTANT: /u changes quantifier behavior in JS
/[😂]{2}/u.test("😂😂")  # true (quantifier on code point)
/[😂]{2}/.test("😂😂")   # unpredictable (code units)

# Case-insensitive Unicode (with /iu)
/\w/iu.test("Ω")  # true (Σ/σ/ς equivalence)

# Validate astral plane characters (emoji, ancient scripts)
/^[\u{1F300}-\u{1F9FF}]+$/u  # emoji range
/[\u{1F600}-\u{1F64F}]/u     # emoticons

Property Escapes \p{...}

Unicode property escapes (\p{L}, \p{N}, \p{Script=Han}, \p{Emoji}) match by character property, not code point range. Far more readable and robust than ranges like [\u4e00-\u9fff]. JS requires /u flag (ES2018+ for properties). Python needs the third-party 'regex' module or Python 3.13+. PHP/Java/.NET/Go support natively.

regex
# Unicode property escapes (require /u in JS)

# \p{L} or \p{Letter} - any letter (any script)
/^\p{L}+$/u.test("Hello")       # true
/^\p{L}+$/u.test("日本語")       # true (CJK letters)
/^\p{L}+$/u.test("Привет")      # true (Cyrillic)

# \p{N} or \p{Number} - any number
/^\p{N}+$/u.test("123")         # true
/^\p{N}+$/u.test("١٢٣")         # true (Arabic-Indic digits)

# \p{P} or \p{Punctuation}
/^\p{P}+$/u.test("!?.")         # true

# \p{S} or \p{Symbol}
/^\p{S}+$/u.test("$€¥")         # true (currency symbols)

# \p{Z} or \p{Separator} (whitespace including Unicode)
/^\p{Z}+$/u.test(" \u3000")    # true (incl. ideographic space)

# Negation: \P{...} (uppercase P)
/^\P{L}+$/u.test("123!")        # true (no letters)

# Specific scripts
\p{Script=Han}      # CJK characters
\p{Script=Latin}    # Latin alphabet
\p{Script=Greek}    # Greek
\p{Script=Cyrillic} # Russian etc.

# Categories (long form)
\p{Letter}    # same as \p{L}
\p{Lowercase_Letter}  # same as \p{Ll}
\p{Uppercase_Letter}  # same as \p{Lu}

# Emoji
\p{Emoji}     # any emoji character
\p{Emoji_Presentation}  # emoji with emoji presentation

# JavaScript support: ES2018+
# Python: needs 'regex' module (PyPI) or Python 3.13+
# PHP: /u flag with \p{L} etc.
# Java: \p{L} (Unicode by default)
# .NET: \p{L} by default
# Go (RE2): \p{L} by default

Letter Property \p{L}

\p{L} matches any letter in any script — Latin, Greek, Cyrillic, CJK, Arabic, Hebrew, etc. Subcategories: \p{Lu} (uppercase), \p{Ll} (lowercase), \p{Lo} (other — CJK ideographs, Arabic). Use \p{L}+ for Unicode-aware word matching instead of \w+ (which is ASCII-only by default).

regex
# \p{L} matches any letter in any script

# Match any word (Unicode-aware)
/\p{L}+/gu          # matches "hello", "日本語", "Привет"

# Match a single Latin letter
/\p{Script=Latin}/u

# Specific letter categories
\p{Lu}  # uppercase letter (A, B, Ω, Ψ)
\p{Ll}  # lowercase letter (a, b, ω, ψ)
\p{Lt}  # titlecase letter (Dž, Lj - rare)
\p{Lm}  # modifier letter (ˆ, ˇ)
\p{Lo}  # other letter (CJK, Arabic, Hebrew)

# JavaScript examples
"Hello Ωμέγα".match(/\p{L}+/gu)  # ["Hello", "Ωμέγα"]
"hello WORLD".match(/\p{Lu}+/gu)  # ["WORLD"]
"Hello".match(/\p{Ll}+/gu)        # ["ello"]

# Validate Unicode identifier
/^[\p{L}_][\p{L}\p{N}_]*$/u

# Match Capitalized Unicode word
/\p{Lu}\p{Ll}*/u   # "Hello", "Ωμέγα" (if Ω is uppercase)

# Count letters
const text = "Hello 世界 123";
const letters = text.match(/\p{L}/gu) || [];
// ["H", "e", "l", "l", "o", "世", "界"]

# Python (with 'regex' module)
import regex
regex.findall(r"\p{L}+", "Hello 世界")

# Split on non-letters (Unicode-aware)
"hello, world!世界".split(/\P{L}+/u)  # ["hello", "world", "世界"]

Number Property \p{N}

\p{N} matches any number: ASCII 0-9, Arabic-Indic ٠-٩, Devanagari ०-९, Roman numerals, fractions. Subcategories: \p{Nd} (decimal digits), \p{Nl} (letter numbers like Ⅷ), \p{No} (other like ½, ²). For ASCII-only, use [0-9] explicitly — \d in JS without /u is ASCII, but with /u matches Unicode digits.

regex
# \p{N} matches any number character in any script

# Match digits in any script
\p{N}+   # 123, ١٢٣ (Arabic-Indic), १२३ (Devanagari), etc.

# Subcategories
\p{Nd}  # decimal digit (0-9, ٠-٩ Arabic, ०-९ Devanagari)
\p{Nl}  # letter number (Roman numerals Ⅷ, etc.)
\p{No}  # other number (fractions ½, superscripts ²)

# JavaScript
"Price: 123, Arabic: ١٢٣".match(/\p{N}+/gu)
# ["123", "١٢٣"]

# Match only ASCII digits
/[0-9]/         # ASCII digit
/\d/           # in JS without /u: same as [0-9]
/\d/u          # with /u: matches Unicode digits too!

# ASCII-only number (always)
/[0-9]+/

# Match decimals (with optional decimal point)
/\p{N}+(?:\.\p{N}+)?/u

# Count digits (any script)
const text = "123 ١٢٣";
const digits = text.match(/\p{Nd}/gu) || [];
// ["1", "2", "3", "١", "٢", "٣"]

# Python
import regex
regex.findall(r"\p{N}+", "123 ١٢٣")

# Extract version numbers (ASCII only)
const v = "Version 1.2.3 released";
v.match(/[0-9]+(?:\.[0-9]+)+/)[0]  # "1.2.3"

# Currency with Unicode digits
/\p{Sc}\p{Nd}+(?:\.\p{Nd}+)?/u  # $123.45, €١٢٣.٤٥

Emoji Matching

Match emoji with \p{Emoji} (ES2018+ in JS, requires /u). Common ranges: \u{1F600}-\u{1F64F} (emoticons), \u{1F300}-\u{1F5FF} (symbols). Real emoji matching is complex — ZWJ sequences (👨‍👩‍👧) join multiple code points, variation selectors (\uFE0F) change presentation, flags are regional indicator pairs. For full emoji matching, use a dedicated library.

regex
# Match emoji (requires /u flag in JS)

# Simple emoji property
/\p{Emoji}/u           # any emoji character
/^\p{Emoji}+$/u.test("👋😀🎉")  # true

# Emoji with presentation (displayed as emoji, not text)
/\p{Emoji_Presentation}/u

# Common emoji ranges (for older engines without \p{Emoji})
/[\u{1F600}-\u{1F64F}]/u  # emoticons
/[\u{1F300}-\u{1F5FF}]/u  # symbols & pictographs
/[\u{1F680}-\u{1F6FF}]/u  # transport & map
/[\u{1F700}-\u{1F77F}]/u  # alchemical
/[\u{2600}-\u{26FF}]/u    # misc symbols (☀ ☂ ☎)
/[\u{2700}-\u{27BF}]/u    # dingbats (✁ ✂ ✃)

# Combined range
/[\u{1F300}-\u{1F9FF}\u{2600}-\u{27BF}]/u

# Count emoji in text
const text = "Hello 👋😀 world 🎉";
const emoji = text.match(/\p{Emoji}/gu) || [];
// ["👋", "😀", "🎉"]

# Strip emoji from text
text.replace(/\p{Emoji}/gu, "")  # "Hello  world "

# Emoji with modifiers (skin tones, ZWJ sequences)
# 👨‍👩‍👧 is actually multiple code points joined by ZWJ (\u200D)
/\p{Emoji}(?:\u200D\p{Emoji})*/u
# matches a ZWJ sequence as one emoji

# Emoji with variation selector (\uFE0F)
/\p{Emoji}\uFE0F?/u  # optional variation selector

# Flag emoji (regional indicators)
/[\u{1F1E6}-\u{1F1FF}]{2}/u  # 🇺🇸, 🇨🇳, etc.

# JavaScript support: ES2018+ (\p{Emoji})
# Python: needs 'regex' module
# PHP: /u flag with \p{Emoji}
19

Language Differences

JavaScript

JavaScript regex: literal /pattern/flags is most common. ES2018+ added lookbehind, named groups, Unicode property escapes. ES2020+ added matchAll (safer than /g+exec). Watch the lastIndex gotcha with /g and exec(). For dynamic patterns, use RegExp constructor. Escape metacharacters when building patterns from user input.

regex
# JavaScript regex features

# Literal syntax (preferred for static patterns)
const re = /pattern/flags;

# Constructor (for dynamic patterns)
const re2 = new RegExp("pattern", "flags");

# Flags: g (global), i (case-insensitive), m (multiline),
#        s (dotAll, ES2018+), u (Unicode), y (sticky)

# Methods
/pattern/.test(string)             # boolean
string.match(/pattern/)            # match info or array
string.match(/pattern/g)           # array of matches (no groups)
[...string.matchAll(/pattern/g)]   # iterator (ES2020+)
string.replace(/pattern/, "repl")  # replace first
string.replace(/pattern/g, "repl") # replace all
string.split(/pattern/)            # split
string.search(/pattern/)           # index of first match or -1

# Named groups (ES2018+)
const m = "2024-01-15".match(/(?<y>\d{4})/);
m.groups.y  # "2024"

# Lookbehind (ES2018+, not supported in old Safari)
/(?<=\$)\d+/.test("$100")  # true

# Unicode property escapes (ES2018+)
/^\p{L}+$/u.test("Hello")  # true

# Gotcha: /g with exec() maintains lastIndex
const r = /a/g;
r.exec("aaa"); r.lastIndex  # 1
r.exec("aaa"); r.lastIndex  # 2
# Use matchAll or reset lastIndex to 0

# String.prototype.replaceAll (ES2021+)
"a-b-c".replaceAll("-", "+")  # "a+b+c"

# Escape function for literal strings
function escapeRegExp(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

Python

Python's re module uses raw strings (r"") to avoid double-escaping. Key functions: search (anywhere), match (anchored at start), fullmatch (entire), findall, finditer, sub (replace), split. Python-specific named group syntax: (?P<name>...) and (?P=name). Python 3.11+ adds regex timeout. The third-party 'regex' module adds \p{...} properties and recursive patterns.

regex
# Python regex (re module)

import re

# Raw strings (r"...") avoid double-escaping
pattern = r"\bword\b"
re.search(pattern, text)  # find anywhere
re.match(pattern, text)   # anchored at start
re.fullmatch(pattern, text)  # entire string

# Flags (constants)
re.IGNORECASE  # or re.I - case-insensitive
re.MULTILINE   # or re.M - ^ $ match line boundaries
re.DOTALL      # or re.S - . matches newline
re.VERBOSE     # or re.X - allow whitespace and comments
re.ASCII       # or re.A - ASCII-only for \w \d \s
re.UNICODE     # default in Python 3

# Combine flags with |
re.search(pattern, text, re.IGNORECASE | re.MULTILINE)

# Inline flags
(?i)pattern    # case-insensitive
(?im)pattern   # multiple inline flags
(?i:pattern)   # scoped (Python 3.6+)

# Common functions
re.search(pattern, string, flags)  # Match or None
re.match(pattern, string, flags)   # Match or None (anchored)
re.fullmatch(pattern, string)      # Match or None (entire)
re.findall(pattern, string)        # list of matches or tuples
re.finditer(pattern, string)       # iterator of Match objects
re.sub(pattern, repl, string, count=0)  # replace
re.split(pattern, string, maxsplit=0)   # split

# Named groups (Python-specific syntax)
(?P<name>pattern)        # capture
(?P=name)                # backreference
\g<name>                 # in replacement

# Compiled pattern (faster for reuse)
p = re.compile(r"\d+", re.IGNORECASE)
p.search("abc 123")

# Timeout (Python 3.11+)
re.compile(r"(a+)+").match(long_string, timeout=1.0)

# Third-party 'regex' module adds: \p{...}, variable lookbehind, recurs

Java

Java regex requires double-escaping in strings ("\\d" for \d). Pattern/Matcher split compile from matching. find() advances to next match; matches() requires entire string to match; lookingAt() anchors at start. Java supports possessive quantifiers (a++) and atomic groups (?>...). Lookbehind must be fixed-length. Java 7+ added named groups.

regex
# Java regex (java.util.regex)

import java.util.regex.*;

# Compile pattern (double-escape backslashes in Java strings)
Pattern p = Pattern.compile("\\bword\\b");
Matcher m = p.matcher("a word here");

# Methods
m.find()        # finds next match (boolean)
m.matches()     # entire string matches (boolean)
m.lookingAt()   # matches at start (boolean)
m.group()       # entire match
m.group(1)      # first capture group
m.start()       # start index
m.end()         # end index

# Replace
m.replaceAll("repl")       # all matches
m.replaceFirst("repl")     # first match only

# String convenience methods (compile internally)
"abc".matches("a.c")           # true (entire string)
"a-b-c".replaceAll("-", "+")   # "a+b+c" (regex)
"a-b-c".replace("-", "+")      # "a+b+c" (literal)
"a-b-c".split("-")             # ["a", "b", "c"]

# Flags (constants)
Pattern.CASE_INSENSITIVE  # or 1 << 1
Pattern.MULTILINE         # or 1 << 2
Pattern.DOTALL            # or 1 << 3
Pattern.UNICODE_CHARACTER_CLASS  # or 1 << 7
Pattern.COMMENTS          # or 1 << 4

# Combine flags with |
Pattern.compile("abc", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE)

# Inline flags
(?i)abc       # case-insensitive
(?id)abc      # multiple
(?i:abc)      # scoped

# Named groups (Java 7+)
(?<name>pattern)
\k<name>      # backreference
${name}       # in replacement

# Lookbehind: must be FIXED length
(?<=ab|cd)x   # OK (same length)
(?<=ab|abc)x  # ERROR (different length)

# Possessive quantifiers (Java supports)
a++  a*+  a?+  a{2,5}+

# Atomic groups
(?>a|b)

# Stream matches (Java 9+)
p.matcher(input).results().forEach(m -> ...);

PHP (PCRE)

PHP uses PCRE with delimiter syntax (/pattern/flags). Functions: preg_match (single match), preg_match_all (all), preg_replace, preg_replace_callback, preg_split. Flags go after closing delimiter (i, m, s, x, u). Named groups: (?P<name>...) or (?<name>...). Use /u flag for UTF-8. Choose a different delimiter (#, ~) if your pattern contains /.

regex
# PHP regex (PCRE - Perl-Compatible Regular Expressions)

# Pattern delimited by / (or other chars like #, ~, |)
/^[a-z]+$/i
'~^[a-z]+$~i'

# Functions
preg_match('/pattern/', $subject)            # 0 or 1 (bool-ish)
preg_match('/pattern/', $subject, $matches)  # fills $matches
preg_match_all('/pattern/', $subject, $matches)  # count, fills $matches
preg_replace('/pattern/', 'repl', $subject)  # replace all
preg_replace_callback('/pattern/', fn, $subject)  # function replace
preg_split('/pattern/', $subject)            # split

# Flags after closing delimiter
i  # case-insensitive
m  # multiline (^ $ at line boundaries)
s  # dotall (. matches newline)
x  # extended (whitespace ignored, # comments)
u  # treat pattern and subject as UTF-8

# Combine: /pattern/imsu

# $matches structure
preg_match('/(\w+)@(\w+)/', 'user@host', $m);
# $m[0] = "user@host" (whole match)
# $m[1] = "user" (group 1)
# $m[2] = "host" (group 2)

# preg_match_all with PREG_SET_ORDER
preg_match_all('/(\w+)/', 'a b c', $m, PREG_SET_ORDER);
# $m[0] = ["a", "a"], $m[1] = ["b", "b"], ...

# Named groups
/(?P<name>\w+)/       # PHP/PCRE style (also (?<name>...))
/(?<name>\w+)/        # alternative
\k<name>              # backreference
$matches['name']       # access by name

# Replacement references
$1, $2          # numbered groups
\1, \2          # also valid
${1}            # unambiguous (e.g., ${1}0 to avoid $10)

# Unicode properties (with /u flag)
preg_match('/^\p{L}+$/u', 'Hello')

# PCRE-specific features
(?P<name>...)   # named (Python-compatible)
(?<name>...)    # named (alternative)
(?P=name)       # backreference
(?>...)         # atomic group
a++             # possessive
\R              # any line break sequence
(*FAIL)         # force fail
(*SKIP)         # skip in findall

# Delimiter alternatives (when pattern contains /)
# /pattern/, #pattern#, ~pattern~, |pattern|, !pattern!

Go (RE2)

Go uses RE2 — linear-time engine with NO backreferences, lookbehind, atomic groups, or possessive quantifiers. In exchange, RE2 is immune to catastrophic backtracking. Methods: MatchString (bool), FindString (first), FindAllString (all), ReplaceAllString, Split. Use raw strings (backticks) to avoid double-escaping. Flags are inline only: (?i), (?m), (?s).

regex
# Go regex (RE2 engine - linear time, no backreferences)

import "regexp"

# Compile pattern (must use raw string `...` or escape backslashes)
re := regexp.MustCompile(`\bword\b`)
re := regexp.MustCompile("\\bword\\b")  # with escaping

# Methods
re.MatchString("a word here")     # bool
re.FindString("a word here")      # first match or ""
re.FindAllString("a b c", -1)     # []string of all matches
re.FindStringSubmatch("user@host") # [whole, group1, group2, ...]
re.FindAllStringSubmatch(text, -1) # [][]string
re.FindStringIndex("abc")         # [start, end] or nil
re.ReplaceAllString("a-b-c", "+") # "a+b+c"
re.ReplaceAllStringFunc(text, func)
re.Split("a,b,c", -1)             # []string

# -1 means "all matches"; positive N limits splits/matches

# Named groups
re := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})`)
m := re.FindStringSubmatch("2024-01")
# m[0]="2024-01", m[1]="2024", m[2]="01"

# Access by name
re.SubexpNames()  # ["", "year", "month"]

# IMPORTANT: RE2 limitations
# - NO backreferences (\1)
# - NO atomic groups (?>...)
# - NO possessive quantifiers (a++)
# - NO lookbehind (?<=...)
# - NO lookahead (?!...) — but (?=...) IS supported
# - NO recursive patterns
# - NO conditional patterns

# In exchange: linear-time guarantee (no catastrophic backtracking)

# Flags (inline only — no separate flags arg)
(?i)           # case-insensitive
(?m)           # multiline
(?s)           # dotall
(?i)pattern    # applies to whole pattern
(?i)abc(?-i)def  # scoped (PCRE-style, not in Go)

# Compile vs MustCompile
re, err := regexp.Compile(pattern)  # returns error
re := regexp.MustCompile(pattern)   # panics on error

# Quote literal string for safe regex use
regexp.QuoteMeta("1.5")  # "1\\.5"

.NET / C#

.NET regex is feature-rich: named groups (?<name>...), balancing groups (unique — can match nested structures like balanced parens), timeouts, MatchEvaluator for function replacements. Use verbatim strings (@"") to avoid double-escaping. RegexOptions.Singleline means dotall (confusing name). .NET supports the most regex features of any major engine.

regex
# .NET regex (System.Text.RegularExpressions)

using System.Text.RegularExpressions;

# Static methods
Regex.IsMatch("abc", "a.c")                 # bool
Regex.Match("abc", "a.c")                   # first match
Regex.Matches("a1 b2", "\d+")              # all matches
Regex.Replace("a-b-c", "-", "+")            # "a+b+c"
Regex.Split("a,b,c", ",")                   # string[]

# Instance (compiled for reuse)
Regex re = new Regex("\\d+", RegexOptions.Compiled);
re.Match("abc 123");
re.Matches("abc 123 456");
re.Replace("abc 123", "X");

# Flags (RegexOptions enum)
RegexOptions.IgnoreCase      # i
RegexOptions.Multiline       # m
RegexOptions.Singleline      # s (dotall - confusing name!)
RegexOptions.ExplicitCapture # only named groups capture
RegexOptions.IgnorePatternWhitespace  # x (extended)
RegexOptions.Compiled        # compile to IL (faster, slower startup)
RegexOptions.CultureInvariant

# Combine with |
new Regex("abc", RegexOptions.IgnoreCase | RegexOptions.Multiline)

# Named groups (.NET style)
(?<name>pattern)       # capture
(?<name1>...) (?<name2>...)  # multiple
\k<name>               # backreference
${name}                # in replacement

# Alternation in group names (balancing groups - .NET specific)
(?<name1-name2>...)    # pop name2 stack into name1

# Group access
Match m = re.Match("2024-01-15");
m.Groups["year"].Value
m.Groups[1].Value

# Replace with MatchEvaluator (function)
Regex.Replace("abc", "\w", m => m.Value.ToUpper())  # "ABC"

# Timeouts (.NET 4.5+)
Regex re = new Regex("a+", RegexOptions.None, TimeSpan.FromSeconds(1));

# Unicode supported by default
Regex.IsMatch("Hello", @"\p{L}+")  # true (note: @"" verbatim string)

# Verbatim strings (@"...") avoid double-escaping
@"\d+"    # same as "\\d+" but cleaner

# Surprisingly powerful: balancing groups for nested matching
# Can match balanced parentheses (unique to .NET)
 @"((?:[^()]+|(?<Open>\()|(?<Close-Open>\)))*)"
20

Advanced Techniques

Atomic Groups

Atomic groups (?>...) prevent backtracking into the group once matched. Useful for performance (avoids catastrophic backtracking) and to lock in matches. Supported in PCRE, Java, .NET, and Python's 'regex' module — NOT in JavaScript. Alternative in JS: refactor the pattern or use specific character classes to avoid backtracking.

regex
# Atomic group: (?>...) - once matched, NO backtracking into it
# Supported in PCRE, Java, .NET — NOT in JavaScript or Python's re

# Greedy with backtracking
\d+a           # in "123a", \d+ takes "123", then backtracks to "12" if needed
(?>\d+)a       # atomic: \d+ takes "123" and CANNOT give back

# When atomic helps: prevent catastrophic backtracking
# Dangerous: (a+)+b on "aaaa...!"
# Safe (atomic): (?>a+)+b  or  (?>a+b)

# Use case: prevent unwanted backtracking in alternation
# Without atomic: tries each alternative, backtracks on fail
(?>cat|category)   # if "cat" matches, won't try "category"

# Practical: match HTML tag without backtracking issues
(?><[a-z][a-z0-9]*\b[^>]*>)

# Quoted string without backtracking issues
(?>"[^"]*")

# Java
Pattern p = Pattern.compile("(?>\\d+)a");

# PHP/PCRE
preg_match('/(?>\d+)a/', '123a')

# .NET
new Regex("(?>\d+)a")

# Python: not in re module; use 'regex' module
import regex
regex.search(r"(?>\d+)a", "123a")

# JavaScript: NOT supported
# Use possessive quantifiers (also not in JS) or refactor pattern

Possessive Quantifiers

Possessive quantifiers (a++, a*+, a?+) are like greedy but never give back matches. Equivalent to atomic groups but shorter. Supported in Java and PCRE — NOT in .NET, JavaScript, or Python's re. Use atomic groups (?>...) as the portable alternative. Both prevent catastrophic backtracking by avoiding unnecessary backtracking.

regex
# Possessive: a++ a*+ a?+ a{n,m}+
# Like greedy but NEVER gives back (no backtracking)

# Greedy (gives back if needed)
\d+a    # "123a": \d+ takes "123", matches a
\d+x    # "123x": \d+ takes "123", then x matches

# Possessive (never gives back)
\d++a   # "123a": \d+ takes "123", matches a
\d++x   # "123x": \d+ takes "123", x fails - NO backtrack, FAIL

# When possessive helps: avoid catastrophic backtracking
# Dangerous: (a+)+  on "aaaaaa!"
# Possessive: (a++)+  or  (?>a+)+
# Both prevent exponential backtracking

# Common possessive patterns
"[^"]*"++     # possessive quoted string
<[a-z]++>      # possessive tag name
\d++\.\d++   # possessive decimal

# Java supports possessive quantifiers
Pattern.compile("\\d++a");

# PHP/PCRE supports possessive
preg_match('/\d++a/', '123a')

# .NET: does NOT support possessive quantifiers directly
# Use atomic groups instead: (?>\d+)a

# Python 're': NOT supported
# Python 'regex' module: supports \d++ ? Actually no, use (?>\d+)

# JavaScript: NOT supported
# Use atomic groups (also not in JS) or refactor

# Comparison: possessive vs atomic
\d++       # possessive quantifier (Java, PCRE)
(?>\d+)    # atomic group (Java, PCRE, .NET)
# Both prevent backtracking; possessive is shorter syntax

Conditional Patterns

Conditional patterns (?(condition)yes|no) match different patterns based on whether a group matched or a lookahead succeeds. Supported in PCRE, .NET, Perl, and Python's 'regex' module — NOT in JavaScript or Java. Workaround in unsupported engines: use alternation, multiple patterns combined in code, or lookarounds.

regex
# Conditional: (?(condition)yes-pattern|no-pattern)
# PCRE, .NET, Perl — NOT JavaScript or Python's re

# Conditional based on group match
(?(1)yes|no)    # if group 1 matched, use yes-pattern; else no-pattern
(?(<name>)yes|no)  # named group version

# Example: match "Mr. Smith" or "Smith" based on title presence
(Mr\.\s+)?(\w+)(?(1)\s+\2|)  # if title, require last name repeated?

# Practical: optional separator
# If first separator was -, second must be -
# If first was /, second must be /
(\d{4})([-/]?)(\d{2})(?(2)\2|[-/]?)(\d{2})

# Conditional based on lookahead
(?(?=\d)\d+|[a-z]+)  # if next is digit, match digits; else letters

# Python 'regex' module supports conditionals
import regex
regex.search(r"(?(1)yes|no)", text)

# .NET supports conditionals
new Regex("(?(1)yes|no)")

# PCRE/PHP supports conditionals
preg_match('/(?(1)yes|no)/', $text)

# Java: does NOT support conditionals directly
# Use alternation and lookarounds as workaround

# JavaScript: NOT supported
# Refactor with alternation:
# (?(1)A|B) becomes: (?:group1A|B) in some cases
# Or use two separate patterns and combine in code

# Real-world: parse key-value where value is conditional
# If key is "url", value must be URL; if "count", value must be number
(url|count):(?(?<=url:)https?://\S+|\d+)

Recursion (PCRE)

Recursive patterns (?R) match nested structures like balanced parentheses, nested HTML, or mathematical expressions. PCRE/Perl/Python's 'regex' module support it directly. .NET uses unique 'balancing groups' instead. JavaScript and Java do NOT support regex recursion — use a parser or stack-based code approach for nested structures.

regex
# Recursive patterns: (?R) or (?N) - match nested structures
# PCRE, Perl, .NET (balancing groups) — NOT JS, Java, Python re

# Match balanced parentheses (PCRE)
\((?:[^()]+|(?R))*\)
# Matches: "(a)", "(a(b)c)", "((a)(b))", etc.

# Named recursion (PCRE)
(?<paren>\((?:[^()]+|(?&paren))*\))

# Match nested HTML/XML tags (PCRE)
(?<tag><([a-z]+)(?:[^>]+)>[^<]*</\2>|<([a-z]+)(?:[^>]+)>(?R)*</\3>)

# Python 'regex' module supports recursion
import regex
regex.search(r"\((?:[^()]+|(?R))*\)", "(a(b)c)")

# .NET: use balancing groups instead of recursion
# Match balanced parens:
\((?:[^()]+|(?<Open>\()|(?<Close-Open>\)))*\)
# More complex but powerful

# PHP/PCRE
preg_match('/\((?:[^()]+|(?R))*\)/', '(a(b)c)')

# JavaScript: NOT supported
# Use a parser or stack-based approach in code
function matchBalanced(s) {
  let depth = 0, start = -1, results = [];
  for (let i = 0; i < s.length; i++) {
    if (s[i] === "(") { if (depth === 0) start = i; depth++; }
    else if (s[i] === ")") {
      depth--;
      if (depth === 0 && start !== -1) {
        results.push(s.slice(start, i + 1));
        start = -1;
      }
    }
  }
  return results;
}

# Java: NOT supported
# Use third-party library or stack-based parser

Performance Tips

Regex performance: compile patterns for reuse, be specific (avoid .), anchor when possible, use non-capturing groups, avoid catastrophic backtracking, prefer negated classes over lazy quantifiers, order alternation by likelihood, set timeouts for untrusted input. Use String methods (trim, split, indexOf) when they suffice — they're often faster than regex.

regex
# 1. Compile patterns for reuse (Python, Java)
import re
p = re.compile(r"\d+")  # compile once
for line in lines:
    p.search(line)  # reuse

# 2. Be specific - avoid . when you know the char class
# Bad:  .*
# Good: [a-z]*  or  \w*  or  [^,]*
# Faster and clearer intent

# 3. Anchor when possible
# Bad:  \d+
# Good: ^\d+$  (if validating entire string)
# Anchors let the engine fail fast

# 4. Use non-capturing groups when you don't need captures
# Bad:  (\d{1,3}\.){3}\d{1,3}
# Good: (?:\d{1,3}\.){3}\d{1,3}
# Saves memory and time

# 5. Avoid catastrophic backtracking
# Bad:  (a+)+b  on "aaaa...!"
# Good: a++b (possessive) or (?>a+)b (atomic) or a*b

# 6. Prefer negated classes over lazy quantifiers for delimited content
# Slower: ".*?"
# Faster: "[^"]*"

# 7. Use atomic groups / possessive quantifiers when safe
(?>\d+)  # no backtracking needed for digits

# 8. Order alternation by likelihood
# Bad:  (rare|common)
# Good: (common|rare)
# Engine tries left to right

# 9. Avoid nested quantifiers on overlapping alternatives
# Bad:  (a|a)*b
# Good: a*b

# 10. Set timeouts for untrusted input (Python 3.11+, .NET 4.5+)
re.compile(pattern).match(input, timeout=1.0)

# 11. Use RE2 for untrusted patterns (Go, or libraries)
# Linear time, immune to catastrophic backtracking
# Trade-off: no backreferences

# 12. Benchmark with realistic data
# Use timeit (Python) or performance.now() (JS)

# 13. Avoid lookahead/lookbehind when a simpler pattern works
# Bad:  (?<=\$)\d+
# Good: \$(\d+) and use group 1 (often faster)

# 14. Use String methods for simple operations
# Bad:  text.replace(/^\s+|\s+$/g, "")
# Good: text.trim()  (much faster)

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.