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.
# 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-whitespaceMetacharacters
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).
# 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" onlyDot & 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.
# 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.
# 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.
# 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")) # TrueCharacter 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.
# 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 digitsNegated 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.
# 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-vowelsRanges [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.
# 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 zPredefined 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.
# 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:]] # whitespaceNegated 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].
# 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-newlineCombining 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.
# 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}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?://
# * 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.
# + 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'.
# ? 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.
# 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.
# 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 '.*?'.
# 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 minimumAnchors & 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.
# ^ 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)$.
# $ 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.
# \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 onlyNon-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.
# \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 edgesString 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.
# 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 RubyGroups & 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.
# 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.
# 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.
# (?:...) 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).
# 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.
# | 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 1234Group 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.
# \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)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.
# (?: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.
# 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 domainNested 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.
# 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.
# 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.
# 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 unquotedLookahead & 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.
# (?=...) - 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 charNegative 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').
# (?!...) - 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)\bPositive 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.
# (?<=...) - 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 lengthNegative 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.
# (?<!...) - 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+\bValidation 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.
# 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.
# 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 lookaheadSubstitution & 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.
# 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 $$.
# 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.
# 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.
# 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.
# 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.
# 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"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.
# 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 allCase-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.
# 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.
# 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.
# 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.
# 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("😂😂") # trueSticky 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).
# 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 ignoredCommon Patterns
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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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")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.
# 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.
# 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.
# 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.
# 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.
# 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)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.
# 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 # invalidDD/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.
# 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 storageTime 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.
# 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.
# 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).
# 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-indexedNumber 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).
# 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}).
# 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.
# 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.00Scientific 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.
# 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.
# 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)$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.
# 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).
# 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.
# 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.
# 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.
# 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 lineRemove 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).