JS: 'bat bet bit'.match(/b.t/g)
Py: re.findall(r'b.t', 'bat bet bit')
output
["bat", "bet", "bit"]
Note The dot does NOT match newline (\n) by default. Use the s (dotall) flag to make dot match newline as well. A common beginner mistake is using . when you mean a literal period -- escape it as \. instead.
any characterdot wildcardmatch anysingle character match
Note Matches position at start of string. With the m (multiline) flag, ^ matches the start of each line instead. Do not confuse this with [^...] inside a character class, which means negation.
start of stringbegins withanchor startcaret anchor
Note Matches position at end of string. With the m flag, $ matches the end of each line. In Python, re.match only checks the start -- use re.search with $ to check the end.
end of stringends withanchor enddollar anchorfile extension check
\b Word Boundary
syntax
\bword\b
example
JS: 'catfish is not a cat'.match(/\bcat\b/g)
Py: re.findall(r'\bcat\b', 'catfish is not a cat')
output
["cat"] (matches only the standalone word, not "catfish")
Note A word boundary is the position between a word character (\w) and a non-word character. Essential for whole-word searches. In JS, remember to double-escape in strings: new RegExp('\\bcat\\b').
word boundarywhole word matchexact word\b boundary
\B Non-Word Boundary
syntax
\Bpattern or pattern\B
example
JS: 'catfish is not a cat'.match(/\Bcat/g)
Py: re.findall(r'\Bcat', 'catfish is not a cat')
output
[] (no match -- 'cat' always starts at a word boundary here)
'scattered'.match(/\Bcat\B/g) => ["cat"] (inside a word)
Note \B matches every position that is NOT a word boundary -- useful for finding substrings embedded within larger words.
non-word boundaryinside word matchembedded match\B boundary
Note All special regex characters must be escaped with a backslash to match literally. The special characters are: . * + ? ( ) [ ] { } ^ $ | \. In Python raw strings (r'...'), you only escape for regex, not for Python itself.
JS: 'gray vs grey'.match(/gr[ae]y/g)
Py: re.findall(r'gr[ae]y', 'gray vs grey')
output
["gray", "grey"]
Note A character class matches exactly one character from the set. Most metacharacters lose their special meaning inside brackets -- for example, [.+*] matches a literal dot, plus, or asterisk.
character setmatch one ofcharacter classbracket expressionalternative characters
JS: null (no uppercase letter followed by digit)
Py: "4B" (digit followed by uppercase letter)
Note Order matters. [a-z] covers lowercase Latin letters. Combine ranges for broader matches. Ranges use Unicode/ASCII code points, so [A-z] accidentally includes [, \, ], ^, _, ` -- always use [A-Za-z] instead.
letter rangedigit rangealphanumericcharacter range
Note The caret ^ must be the first character inside the brackets to negate. Placed anywhere else, it matches a literal ^. Common gotcha: [^abc] still matches newlines unless excluded.
negated classnot matchingexclude characterscaret in brackets
Note In Python 3 with the default engine, \d can match Unicode digits (e.g., Arabic-Indic digits) unless you use re.ASCII. In JavaScript, \d always means [0-9].
match digitsextract numbersnon-digit\d shorthandnumeric match
Note \w includes the underscore. Like \d, Python's \w matches Unicode letters by default (accented chars, CJK, etc.) while JS \w sticks to ASCII unless the u flag is combined with Unicode property escapes.
word characteralphanumericunderscorenon-word character\w shorthand
\s Whitespace and \S
syntax
\s => [ \t\n\r\f\v] \S => [^ \t\n\r\f\v]
example
JS: 'hello world'.split(/\s+/)
Py: re.split(r'\s+', ' line one\n line two ')
Note \s matches spaces, tabs, newlines, carriage returns, form feeds, and vertical tabs. In Python, re.split on a leading/trailing whitespace produces empty strings at the edges -- use str.split() if you want to avoid them.
Note Inside a character class, the dot loses its special meaning and matches a literal period. Both [.] and \. work for matching periods, but [.] can be more readable.
literal dotdot in bracketsmatch periodcharacter class dot
Note [\s\S] is a classic trick to match ANY character including newlines (before the s flag existed). Place a hyphen at the start or end of a class [-abc] or [abc-] to match it literally, or escape it [a\-z].
hex digitscustom rangecombine classesmatch everythinghyphen in class
JS: 'caaandy'.match(/ca*/)
Py: re.findall(r'bo*', 'A bird booed and a bobcat roared')
output
JS: "caaa"
Py: ["boo", "bo", "b"]
Note * is greedy by default -- it matches as many characters as possible. Beware: .* can match empty strings and lead to unexpected results at every position in a string.
zero or morestar quantifierrepeat zero plusasterisk
+ One or More
syntax
a+ matches one or more 'a' characters
example
JS: 'caaandy candy cdy'.match(/ca+ndy/g)
Py: re.findall(r'\d+', 'There are 12 cats and 340 dogs')
output
JS: ["caaandy", "candy"]
Py: ["12", "340"]
Note Unlike *, the + quantifier requires at least one occurrence. This is the most common quantifier for extracting sequences of digits, letters, etc.
one or moreplus quantifierat least onerepeat one plus
? Optional (Zero or One)
syntax
a? matches zero or one 'a'
example
JS: 'color colour'.match(/colou?r/g)
Py: re.findall(r'https?://', 'http://a.com and https://b.com')
Note Useful for minimum-length validation. Remember this is greedy -- it will consume as much as possible.
minimum countat least n timesn or moreminimum length
Greedy vs Lazy Matching
syntax
Greedy: .* .+ .? Lazy: .*? .+? .??
example
JS: '<b>bold</b> and <b>more</b>'.match(/<b>.*<\/b>/)
JS: '<b>bold</b> and <b>more</b>'.match(/<b>.*?<\/b>/)
output
Greedy: "<b>bold</b> and <b>more</b>" (longest match)
Lazy: "<b>bold</b>" (shortest match)
Note Greedy quantifiers eat as much as possible, then backtrack. Lazy quantifiers match as little as possible, then expand. This distinction is critical when parsing HTML, quoted strings, or any delimited content. Always prefer lazy when you want the nearest closing delimiter.
greedy matchinglazy matchingnon-greedyminimal matchshortest matchlongest match
Lazy Quantifier Variants
syntax
*? +? ?? {n,m}? {n,}?
example
JS: '"hello" and "world"'.match(/"[^"]+?"/g)
Py: re.findall(r'\d{2,4}?', '12345')
Note Append ? after any quantifier to make it lazy. {2,4}? prefers matching 2 characters instead of 4. For the quoted-string case, a negated class [^"]+ is often more efficient than a lazy .+? approach.
*+ ++ ?+ {n,m}+ (not supported in JS or Python re)
example
Java/PCRE: '"hello"'.match(/"[^"]*+"/)
Python alternative: use atomic group via regex module
import regex
regex.search(r'"(?>"[^"]*)"+', text)
output
Matches "hello" without backtracking into the quoted content
Note Possessive quantifiers never give back characters once matched. They prevent catastrophic backtracking but cause a match to fail if the rest of the pattern cannot match. Not available in JavaScript or Python's built-in re module -- use the third-party 'regex' module in Python or atomic groups in PCRE.
Note Each pair of parentheses creates a numbered capture group starting at 1. In JS, the full match is at index 0. Groups are essential for extracting parts of a match for later use.
capture groupextract partsparentheses groupsub-matchparse date
Non-Capturing Group (?:)
syntax
(?:pattern) groups without capturing
example
JS: 'http://a.com https://b.com'.match(/(?:https?):\/\/\S+/g)
Py: re.findall(r'(?:Mr|Mrs|Ms)\.\s(\w+)', 'Mr. Smith and Ms. Lee')
output
JS: ["http://a.com", "https://b.com"]
Py: ["Smith", "Lee"] (only the name is captured)
Note Use (?:) when you need grouping for alternation or quantifiers but do not need to capture the result. Reduces memory usage and keeps group numbering clean.
non-capturing groupgroup without captureefficient groupingalternation group
Named Capturing Groups
syntax
JS: (?<name>pattern) Python: (?P<name>pattern)
example
JS: const m = '2026-04-04'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
m.groups.year // "2026"
Py: m = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2026-04-04')
m.group('year') # '2026'
Note Syntax differs between JS and Python. JS uses (?<name>...) while Python uses (?P<name>...). Named groups dramatically improve readability of complex patterns. Both approaches also assign a numeric index.
named groupnamed capturegroup namereadable regexlabeled group
Backreferences \1
syntax
\1 \2 etc. refer to previously captured groups
example
JS: 'abcabc'.match(/(abc)\1/)
Py: re.search(r'(\w+)\s+\1', 'the the quick brown fox')
output
JS: ["abcabc", "abc"]
Py: matches "the the" (detects duplicate word)
Note The backreference matches the exact same text captured by group N, not the same pattern. Useful for detecting repeated words or matched delimiters (like matching opening and closing quotes of the same type).
backreferencerepeated groupduplicate wordsame text again\1 reference
Named Backreferences
syntax
JS: \k<name> Python: (?P=name)
example
JS: /(?<quote>['"]).*?\k<quote>/.exec(`She said "hello" and 'bye'`)
Py: re.search(r"(?P<quote>['\"]).*?(?P=quote)", 'He said "ok"')
output
JS: ['"hello"', '"']
Py: '"ok"'
Note Named backreferences are more readable than \1 in complex patterns. Syntax differs: JS uses \k<name>, Python uses (?P=name).
named backreferencenamed back referencematch same quote\k reference
Alternation |
syntax
pattern1|pattern2
example
JS: 'My cat and your dog'.match(/cat|dog/g)
Py: re.findall(r'\b(?:jpg|png|gif)\b', 'files: logo.png and bg.jpg')
output
JS: ["cat", "dog"]
Py: ["png", "jpg"]
Note The | operator has very low precedence -- /ab|cd/ matches 'ab' or 'cd', NOT 'a(b|c)d'. Always wrap alternations in a group when they are part of a larger pattern to avoid surprises.
JS: '192.168.1.1'.match(/((\d{1,3})\.){3}(\d{1,3})/)
Py: m = re.search(r'((\d{1,3})\.){3}(\d{1,3})', '192.168.1.1')
m.group(0) # '192.168.1.1'
output
Full match: "192.168.1.1"
Group 1: "1." (last repetition of the outer group)
Group 2: "1" (last repetition of the inner group)
Group 3: "1"
Note When a group is repeated with a quantifier, only the LAST capture is retained. If you need all repetitions, use findall or matchAll instead. Numbering is left-to-right by opening parenthesis.
["100", "50"] (numbers followed by 'px', but 'px' is not in the match)
Note Lookaheads are zero-width assertions -- they check what comes next without consuming characters. The looked-ahead text is not part of the match result. This means subsequent patterns can still match those characters.
positive lookaheadfollowed byassert aheadzero-width assertionconditional match
Negative Lookahead (?!)
syntax
pattern(?!ahead) matches pattern only if NOT followed by ahead
JS: ["3", "5"] (numbers NOT followed by ' cats')
Py: ["data.csv", "img.png"] (files that are NOT .exe)
Note Negative lookahead is excellent for excluding specific patterns. Watch for off-by-one issues -- ensure the lookahead checks the right position. Commonly used to exclude file types, keywords, or invalid suffixes.
negative lookaheadnot followed byexclude patternreject match
Positive Lookbehind (?<=)
syntax
(?<=behind)pattern matches pattern only if preceded by behind
example
JS: '$100 and EUR200'.match(/(?<=\$)\d+/g)
Py: re.findall(r'(?<=@)\w+', 'user@gmail and admin@corp')
output
JS: ["100"] (digits preceded by $)
Py: ["gmail", "corp"] (words preceded by @)
Note Lookbehinds check what comes BEFORE the match position without including it. In JavaScript, lookbehinds were added in ES2018 and are supported in all modern browsers. Python's re module requires fixed-width lookbehinds -- use the third-party regex module for variable-width.
positive lookbehindpreceded byafter prefixlook behindextract after symbol
Negative Lookbehind (?<!)
syntax
(?<!behind)pattern matches pattern only if NOT preceded by behind
example
JS: 'USD 100 and $200'.match(/(?<!\$)\b\d+/g)
Py: re.findall(r'(?<!\\)\n', 'line1\nline2\\\nline3')
output
JS: ["100"] (digits NOT preceded by $)
Note Useful for avoiding matches that follow a specific prefix. A classic use case is matching newlines that are not escaped (not preceded by a backslash).
negative lookbehindnot preceded byexclude prefixavoid after
Combining Lookaheads & Lookbehinds
syntax
(?<=prefix)pattern(?=suffix)
example
JS: 'price: [42] and size: [XL]'.match(/(?<=\[)\d+(?=\])/g)
Py: re.findall(r'(?<=\[)\w+(?=\])', '[hello] and [world]')
Note You can combine multiple lookarounds on the same position. A common pattern for password validation stacks multiple lookaheads: (?=.*[A-Z])(?=.*\d)(?=.*[@#$]).{8,} checks for uppercase, digit, and special character all in one pass.
true / True for 'MyP@ss1word', false / False for 'weakpass'
Note Each lookahead checks a different requirement without advancing the match position. All must pass before .{8,} consumes the string. This is a widely-used pattern but be cautious about catastrophic backtracking with very long inputs -- consider checking each requirement separately in code for production validation.
Note Without the m flag, ^ matches only the very beginning of the entire string. With the m flag, it matches the start of each line (after every newline character).
Note A word boundary exists at three positions: before the first \w character, after the last \w character, and between a \w and \W character. It does NOT match any characters itself -- it matches a position.
word boundary detailwhole wordstandalone wordboundary position
\A Absolute Start of String (Python)
syntax
\A matches the very start of the string (ignores multiline)
\A always means absolute start, even with re.MULTILINE
Note \A is not available in JavaScript. It is equivalent to ^ when multiline mode is off, but unlike ^, it never changes behavior with the m flag. Use \A in Python when you always mean the beginning of the entire string.
\Z matches only the absolute end, regardless of flags
Note In Python, \Z matches the absolute end (after the last character). Note: \z (lowercase) is used in some other flavors (Ruby, PCRE) for the same purpose, while Python's \Z (uppercase) does not match before a trailing newline. Not available in JavaScript.
absolute end\Z anchorpython endignore multiline end
["line1", "line2", "line3"] (each line start matched)
Note Without the m flag, only 'line1' would match ^\w+. A common mistake is forgetting the m flag when processing multi-line text like log files or configuration files. In Python, re.MULTILINE or re.M is the flag; in JS, use /m.
Note This is a practical pattern covering the vast majority of real email addresses. The full RFC 5322 spec is far more complex and almost never needed. For production validation, prefer a library or send a confirmation email.
match emailvalidate emailemail regexextract email addressemail pattern
Note URL matching is inherently tricky due to the wide range of valid characters. This pattern handles most common HTTP/HTTPS URLs. For rigorous URL validation, use the URL constructor in JS or urllib.parse in Python.
match URLvalidate URLextract URLHTTP linkweb address pattern
JS: '192.168.1.1 and 999.999.999.999'.match(/\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g)
Py: re.findall(r'\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b', text)
output
["192.168.1.1"] (999.999.999.999 rejected -- octets must be 0-255)
Note Each octet allows 0-255. The alternation order matters: check 25[0-5] before 2[0-4]\d before the general case. A simpler but less accurate version is \d{1,3}(\.\d{1,3}){3} which does not validate octet ranges.
IP addressvalidate IPIPv4 patternmatch IPnetwork address
Note Phone formats vary wildly by country. This covers common US formats: (555) 123-4567, 555-123-4567, 555.123.4567, +1 555 123 4567. For international numbers, consider a library like libphonenumber.
phone numbervalidate phonematch phonetelephone regexUS phone format
JS: '2026-04-04 and 12/25/2025'.match(/\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])/g)
Py: re.findall(r'(?:0[1-9]|1[0-2])/(?:0[1-9]|[12]\d|3[01])/\d{4}', text)
output
JS: ["2026-04-04"]
Py: ["12/25/2025"]
Note Regex validates format but not logical correctness -- it will accept 02/31/2026 (Feb 31 does not exist). For actual date validation, parse the match with Date (JS) or datetime (Python) afterward.
date formatvalidate datematch dateYYYY-MM-DDparse datedate regex
Note Rules: must start with a letter, 3-20 characters total, allows letters, digits, dots, underscores, and hyphens. Adjust the {2,19} range for your app's requirements (first char + 2-19 more = 3-20 total).
Note Matches both 3-digit (#RGB) and 6-digit (#RRGGBB) hex colors. The {1,2} quantifier on the 3-char group handles both lengths. Does not cover 8-digit RGBA hex -- extend to {1,2,} won't work; add a separate alternation for #RRGGBBAA if needed.
hex colorcolor codevalidate hexCSS colormatch color
Note Captures only the last extension. For double extensions like .tar.gz, match /\.tar\.gz$/ explicitly or extract both with /\.([a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*)$/. Alternatively, use path.extname (Node) or os.path.splitext (Python).
Full: "<div class=\"box\">content</div>"
Group 1: "div", Group 2: "content"
Note This works for simple, non-nested cases only. Regex CANNOT reliably parse nested HTML -- nested tags, self-closing tags, comments, and CDATA sections break pattern-based approaches. Use a proper HTML parser (DOMParser, BeautifulSoup, cheerio) for production code.
HTML tagmatch tagextract HTMLparse HTMLtag content
Whitespace Trimming
syntax
/^\s+|\s+$/g
example
JS: ' hello world '.replace(/^\s+|\s+$/g, '')
Py: re.sub(r'^\s+|\s+$', '', ' hello world ')
output
"hello world"
Note Equivalent to str.trim() in JS and str.strip() in Python. The regex version is useful when you also want to normalize internal whitespace: replace /\s+/g with ' ' after trimming.
trim whitespacestrip spacesremove leading trailingclean whitespace
Note Validates US-formatted numbers with optional commas every 3 digits and optional decimal part. European formats swap comma and period -- adjust the pattern accordingly. The leading -? handles negative numbers.
number formatcomma separated numbervalidate numberdecimal numberformatted number
JS: 'aaa'.match(/a/g)
JS: 'aaa'.match(/a/) // without g
Py: re.findall(r'a', 'aaa') # global by nature
output
With g: ["a", "a", "a"]
Without g: ["a"] (first match only)
Python findall: ['a', 'a', 'a']
Note In JS, the g flag makes match() return all matches instead of just the first. Without g, match() returns the first match with capture groups. Python's re.findall and re.sub are inherently global. Use re.search for first-match-only behavior in Python.
global flagfind all matchesg flagmultiple matchesmatch all
Note Affects all literal characters and character classes in the pattern. [a-z] with the i flag will also match uppercase letters. Be aware that case-folding in Unicode can be surprising -- the German sharp s (ss) may match SS in some engines.
Note Changes ^ and $ from matching string start/end to matching line start/end. Does NOT change the behavior of \A and \Z in Python, nor does it affect how the dot treats newlines. Combine with g in JS for all line-start matches.
multiline flagm flagline by lineper line matchmultiline regex
Without s: no match (dot skips \n)
With s: "line1\nline2"
Note The s flag makes the dot (.) match newline characters too. Before this flag existed, the workaround was [\s\S] to match any character including newlines. The s flag was added to JS in ES2018.
dotall flags flagdot matches newlinesingle line modematch across lines
With u: emoji treated as single character
Without u: emoji seen as two surrogate code units
Note In JS, the u flag enables full Unicode matching: surrogate pairs are treated as single code points, and Unicode property escapes (\p{...}) become available. Python 3 handles Unicode by default. Always use the u flag in JS when dealing with international text or emoji.
JS: const re = /\d+/y;
re.lastIndex = 4;
re.exec('abc 123 xyz') // ["123"] (starts exactly at index 4)
re.exec('abc 123 xyz') // null (index 7 is a space, not a digit)
output
First exec: "123" at index 4
Second exec: null (must match exactly at lastIndex)
Note The y flag forces the match to start at lastIndex, unlike g which searches forward from lastIndex. Useful for building tokenizers/lexers where you need sequential, contiguous matching. No Python equivalent -- Python's re.match already anchors to the start.
sticky flagy flaglastIndextokenizerlexeranchored match
Note Allows whitespace and comments in the pattern for readability. Spaces and # comments are ignored unless escaped or inside a character class. No native JS equivalent, but you can concatenate strings or use template literals to achieve similar readability.
Note Returns true/false. The fastest way to check if a pattern matches. Warning: when using test() with the g flag, the regex remembers lastIndex between calls, which can cause alternating true/false results. Avoid g with test() or reset lastIndex = 0.
test methodboolean matchcheck patternvalidate regexregex test
// Without g: returns first match + groups'Price: $42.99'.match(/(\$)(\d+\.\d{2})/)
// With g: returns all matches (no groups)'$42.99 and $18.50'.match(/\$\d+\.\d{2}/g)
output
Without g: ["$42.99", "$", "42.99"]
With g: ["$42.99", "$18.50"]
Note Behavior changes completely depending on the g flag. Without g, you get detailed info (groups, index). With g, you get a flat array of all matches but lose group details. Use matchAll for both multiple matches AND group info.
match methodfind matchesstring matchextract matchesregex match
JS: matchAll() - Iterate All Matches
syntax
string.matchAll(/pattern/g) => iterator of match objects
example
const text = 'dates: 2026-04-04, 2025-12-25';
for (const m of text.matchAll(/(\d{4})-(\d{2})-(\d{2})/g)) {
console.log(m[0], '=> year:', m[1]);
}
Note Returns an iterator, so use for...of or [...spread]. Requires the g flag or it throws an error. This is the modern replacement for the while(exec()) loop pattern.
matchAlliterate matchesall matches with groupsregex iterator
// Backreference in replacement'John Smith'.replace(/(\w+) (\w+)/, '$2, $1')
// Function replacement'hello'.replace(/\w/g, c => c.toUpperCase())
output
"Smith, John"
"HELLO"
Note In the replacement string, $1 $2 refer to captured groups, $& is the full match, $` is before-match, $' is after-match. Use a function for dynamic replacements. replaceAll() requires the g flag on regex arguments.
regex replacestring replacefind and replacesubstitutionbackreference replace
JS: split() with Regex
syntax
string.split(/pattern/)
example
'one, two; three|four'.split(/[,;|]\s*/)
output
["one", "two", "three", "four"]
Note Split accepts a regex as the separator. If the regex contains capturing groups, the captured text is included in the result array. This can be surprising -- use non-capturing groups (?:) if you don't want separator parts in the output.
regex splitsplit stringtokenizeseparate by pattern
JS: exec() - Detailed Match
syntax
regex.exec(string) => match object ornull
example
const re = /(?<word>\w+)/g;
let m;
while ((m = re.exec('hello world')) !== null) {
console.log(`Found "${m[0]}" at index ${m.index}`);
}
output
"Found \"hello\" at index 0"
"Found \"world\" at index 6"
Note Returns one match at a time with full detail (groups, index, named groups). With the g flag, successive calls advance through the string via lastIndex. Modern code should prefer matchAll() over the exec() while loop.
exec methodregex execiterate execmatch indexdetailed match
Python: re.search() - First Match
syntax
re.search(pattern, string) => Match object orNone
example
import re
m = re.search(r'(\d+)\s*(kg|lb)', 'Weight: 75 kg')
if m:
print(m.group(0), m.group(1), m.group(2))
output
"75 kg" "75" "kg"
Note Scans the entire string and returns the first match. Use re.match() if you only want to check the beginning of the string. Always check for None before calling .group() to avoid AttributeError.
python searchre.searchfirst match pythonfind pattern python
Python: re.findall() - All Matches
syntax
re.findall(pattern, string) => list of strings or tuples
example
import re
re.findall(r'\b[A-Z][a-z]+\b', 'Alice met Bob in Paris')
output
["Alice", "Bob", "Paris"]
Note Returns a list of all non-overlapping matches. If the pattern has groups, findall returns a list of groups (or tuples for multiple groups) instead of the full match. Use (?:) for groups you don't want captured in the result.
Note In the replacement, \1 or \g<1> references group 1, \g<name> references named groups. Pass a function as replacement for dynamic logic. Use count=1 to replace only the first occurrence (similar to omitting the g flag in JS).
import re
re.split(r'[,;]\s*', 'apples, oranges; bananas, grapes')
output
["apples", "oranges", "bananas", "grapes"]
Note Like JS split, captured groups are included in the result. Empty matches at the start or end of the string produce empty strings in the output list. Use maxsplit to limit the number of splits.
python splitre.splitsplit by regex pythontokenize python
(?>pattern) (PCRE/Java; not native in JS or Python re)
example
PCRE: (?>\d+)\s matches '123 ' but NOT by backtracking into digits
Python (regex module):
import regex
regex.search(r'(?>\d+):', '123:')
output
Matches '123:' -- once the digits are consumed, the engine cannot give them back
Note Atomic groups discard all backtracking positions once the group matches. They are a performance optimization to prevent catastrophic backtracking. In Python, use the third-party 'regex' module. In JS, there is no direct equivalent -- simulate with a possessive quantifier mindset or restructure the pattern.
Py (regex module):
import regex
# Match optionally quoted word -- closing quote only if opening exists
regex.findall(r'(")?(\w+)(?(1)")', '"hello" world "test"')
output
Matches '"hello"' and '"test"' as quoted, 'world' as unquoted
Note Conditional patterns branch based on whether a group participated in the match. Python's built-in re supports (?(id)yes|no) syntax. Not available in JavaScript. Useful for matching balanced optional delimiters.
conditional regexif then else regexconditional patternbranch pattern
Note Recursive patterns can match nested structures like balanced parentheses or nested HTML. Not available in JavaScript or Python's built-in re -- requires the third-party regex module for Python or PCRE-based engines. Extremely powerful but can be hard to debug.
Note Common categories: \p{L} (letter), \p{N} (number), \p{P} (punctuation), \p{S} (symbol), \p{Z} (separator), \p{Emoji}. In JS, requires the u flag. Python's built-in re does not support \p{} -- use the regex module. Essential for internationalized text processing.
unicode property\p{L}unicode categoryinternational textemoji matchunicode letter
Note Named group references in replacements improve readability over numeric \1, $1 references. JS uses $<name> in the replacement string while Python uses \g<name>. Works with both replace() and re.sub().
named group replacenamed reference replacementreadable replacementnamed backreference replace
Non-Greedy Scanning Patterns
syntax
.*?target (scan forward minimally until target is found)
example
JS: 'START data1 END noise START data2 END'.match(/START(.*?)END/g)
Py: re.findall(r'START(.*?)END', 'START data1 END noise START data2 END')
Note The .*? idiom scans forward character by character until the following pattern matches. This is the standard approach for extracting content between delimiters. Be aware that .*? can still cause slowdowns on long strings with no match -- consider using negated character classes instead when possible.
non-greedy scanbetween delimitersextract betweenlazy scanminimal match
Compiling Regex for Reuse
syntax
JS: const re = new RegExp(pattern, flags)
Python: re.compile(pattern, flags)
example
JS: const dateRe = new RegExp('\\d{4}-\\d{2}-\\d{2}', 'g');
dateRe.test('2026-04-04');
Py: date_re = re.compile(r'\d{4}-\d{2}-\d{2}')
date_re.findall('2026-04-04 and 2025-12-25')
output
JS: true
Py: ['2026-04-04', '2025-12-25']
Note Compiling is beneficial when the same pattern is used repeatedly (e.g., in a loop). Python caches the most recent patterns automatically, but explicit compilation is clearer and avoids cache eviction. In JS, the RegExp constructor requires double-escaping backslashes in string form.
JS: // DANGER: This can freeze your browser/server!// /(a+)+$/.test('aaaaaaaaaaaaaaaaaaaaaaaaaab')// The engine tries exponential combinations before failing
Py: # Same risk: re.search(r'(a+)+$', 'aaa...ab')
output
Extremely slow or hangs -- exponential backtracking
Note Catastrophic backtracking occurs when the regex engine explores an exponential number of paths. It happens with nested quantifiers applied to overlapping patterns. Prevention: avoid nested quantifiers on similar character sets, use atomic groups or possessive quantifiers, add anchors, or restructure the pattern. This is one of the most common causes of regex-based DoS (ReDoS) vulnerabilities.
catastrophic backtrackingregex performanceReDoSslow regexexponential backtrackingregex hang
Greedy grabs everything, lazy stops at first delimiter
Note Developers often default to .* when .*? or a negated character class [^<]* would be correct. Rule of thumb: if you want to match UP TO a delimiter, either use lazy quantifiers or a negated class that excludes the delimiter character.
greedy lazy mistaketoo much matchedover-matchinggreedy trap
Forgetting to Escape the Dot
syntax
. matches ANY character, \. matches a literal period
example
// Wrong: matches 'a.b' but also 'axb', 'a5b', etc.
/a.b/.test('axb') // true (unintended!)// Correct: matches only 'a.b'
/a\.b/.test('axb') // false
/a\.b/.test('a.b') // true
output
Unescaped dot matches too broadly
Note This is the most frequent regex beginner mistake. Domains, IP addresses, file extensions, and version numbers all contain literal dots that must be escaped. When reviewing regex, always check that dots are escaped where literal periods are intended.
\[ \] for literal brackets, [...] creates a character class
example
// Wrong: trying to match '[INFO]'
/[INFO]/.test('N') // true! It's a character class matching I, N, F, or O// Correct:
/\[INFO\]/.test('[INFO] Server started') // true
output
[INFO] without escaping matches any single character I, N, F, or O
Note Square brackets define character classes in regex. To match literal brackets, escape both opening and closing brackets. This mistake often appears when parsing log files with bracketed labels like [ERROR] or [WARN].
escape bracketsliteral bracketscharacter class mistakebracket error
// ^ outside brackets: start-of-string anchor
/^cat/.test('category') // true (starts with 'cat')// ^ inside brackets: negation
/[^cat]/.test('dog') // true (matches 'd', 'o', 'g' -- none are c, a, or t)
output
Same symbol, completely different meanings based on context
Note The ^ character serves double duty. Outside a character class it anchors to the start of the string. As the first character inside [...], it negates the class. Anywhere else inside [...], it matches a literal caret. This dual meaning catches many beginners.
caret confusion^ meaningnegation vs anchorcaret inside brackets
Multiline Flag Misconceptions
syntax
/m changes ^ and $ only -- NOT the dot
example
// Common mistake: thinking 'm' makes dot match newlines'line1\nline2'.match(/line1.line2/m) // null! Dot still skips \n// You need the 's' flag for that:'line1\nline2'.match(/line1.line2/s) // matches
output
m flag: affects ^/$ only
s flag: affects dot only
Note The m (multiline) flag only changes ^ and $ to match line boundaries. It does NOT make the dot match newlines -- that is the s (dotall) flag. This is one of the most common sources of confusion, especially for developers coming from editors where 'multiline' implies dot-matches-all.
multiline confusionm vs s flagdot newlineflag confusionmultiline misconception
Over-Complex Patterns
syntax
Avoid: /^(?:(?:[a-zA-Z0-9._%+-]+)@(?:(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}))$/
Prefer: split into steps or use a validation library
example
// Instead of one monster regex for URL validation:function isValidUrl(str) {
try { new URL(str); returntrue; }
catch { returnfalse; }
}
# Python: use urllib.parsefrom urllib.parse import urlparse
def is_valid_url(s):
r = urlparse(s)
return bool(r.scheme and r.netloc)
output
Simpler, more maintainable, and fewer edge case bugs
Note Regex is powerful but not always the right tool. When a pattern grows beyond 50-80 characters, consider breaking it into multiple smaller regex checks, using built-in parsers (URL, email, date), or using a purpose-built library. Readability and maintainability matter -- a future developer (including you) will need to understand and modify it.
complex regexregex alternativereadabilitymaintainabilitywhen not to use regexsimplify pattern
Global Flag + test() Stateful Bug
syntax
/pattern/g.test() remembers lastIndex between calls
example
JS: const re = /a/g;
re.test('abc') // true (lastIndex becomes 1)
re.test('abc') // false! (searches from index 1, no 'a' found)
re.test('abc') // true (lastIndex reset to 0 after failure)
output
true, false, true -- alternating unexpectedly
Note Using a regex literal with the g flag and test() or exec() in a loop or repeated calls causes this stateful bug because lastIndex persists. Fix: omit the g flag for test(), or create a new regex each time, or manually reset re.lastIndex = 0 before each test.
lastIndex bugglobal test bugstateful regexalternating resultsregex gotcha