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