/compat/regex/regex-features-by-engine
Regex features by engine: JavaScript, Python, PCRE and POSIX ERE
verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · grep (GNU grep) 3.0updated 2026-10-20
- tool
- Regex
- versions
- Node.js 24, Python 3.13, PCRE2, POSIX ERE
- os
- any
The core syntax (classes, quantifiers, groups, alternation) is the same everywhere, but the advanced features are not. Python's re writes named groups as (?P<name>…) and has no \p{…}; JavaScript has no atomic groups or possessive quantifiers; POSIX ERE (grep -E, awk, sed -E) has no lookarounds at all.
Tested in JavaScript and Python
cat > cases.tsv <<'EOF'
lookahead foo(?=bar) foobar
lookbehind (?<=\$)\d+ $42
named group (?<n>) (?<year>\d{4}) 2026-10
named group (?P<n>) (?P<year>\d{4}) 2026-10
unicode \p{L} \p{L}+ café
atomic group (?>a+)b aaab
possessive a++ a++b aaab
backreference (\w)\1 book
EOF
node -e 'console.log("node", process.version)'; python -c 'import sys; print("python", sys.version.split()[0])'
printf '%-22s %-12s %s\n' FEATURE JAVASCRIPT PYTHON
while IFS=$'\t' read -r name re text; do
js=$(node -e 'try { const m = new RegExp(process.argv[1], "u").exec(process.argv[2]); console.log(m ? m[0] : "no match") } catch { console.log("error") }' "$re" "$text")
py=$(python -c 'import re, sys
try:
m = re.search(sys.argv[1], sys.argv[2]); print(m.group(0) if m else "no match")
except re.error:
print("error")' "$re" "$text")
printf '%-22s %-12s %s\n' "$name" "$js" "$py"
done < cases.tsvOutput
node v24.20.0
python 3.13.15
FEATURE JAVASCRIPT PYTHON
lookahead foo foo
lookbehind 42 42
named group (?<n>) 2026 error
named group (?P<n>) error 2026
unicode \p{L} café error
atomic group error aaab
possessive a++ error aaab
backreference oo ooerror means the engine rejects the syntax. Python accepts atomic groups and possessive quantifiers since 3.11.
Full table
| Feature | JavaScript | Python re | PCRE2 (grep -P, PHP) | POSIX ERE (grep -E) |
|---|---|---|---|---|
| lookahead (?= ) (?! ) | yes | yes | yes | no |
| lookbehind (?<= ) (?<! ) | yes, any length | yes, fixed length | yes, bounded length | no |
| named groups | (?<n>…) | (?P<n>…) | both | no |
| \p{L} Unicode properties | with u or v flag | no (use the regex package) | yes | no |
| atomic (?>…), possessive a++ | no | yes (3.11+) | yes | no |
| backreferences \1 | yes | yes | yes | yes in grep, not in all ERE tools |
| lazy quantifiers *? | yes | yes | yes | no |
| \d \w \s | yes | yes (Unicode by default) | yes | use [[:digit:]] [[:alnum:]_] [[:space:]] |
Sources
Related pages
cheat sheets · regexRegex cheat sheet: characters, quantifiers, groups and lookaroundsRegex cheat sheet: character classes, anchors, quantifiers, greedy and lazy matching, groups and backreferences, lookarounds, with tested Python examples.flags · regexJavaScript regex flags: g, i, m, s, u, v, y and dWhat each JavaScript RegExp flag does: g finds all matches, i ignores case, m works per line, s lets . match newlines, u and v add Unicode, plus y and d.glossary · regexGreedy vs lazy quantifiers in regexA greedy quantifier such as .* matches as much as it can and backtracks only when needed; a lazy one such as .*? matches as little as possible. Examples.