Dialects and engines: PCRE, POSIX, RE2 and .NET
Why the same pattern behaves differently in different tools, and how to write patterns that survive a move between engines.
Who implements what
| Engine | Used by | Distinguishing traits |
|---|---|---|
| PCRE2 | PHP, grep -P, nginx, many editors | the widest feature set: recursion, conditionals, atomic groups |
| JavaScript | browsers, Node | no inline flags, no possessive quantifiers, \p{...} with u |
| Python re | the re module | fixed-width lookbehind, verbose mode, no possessive quantifiers |
| RE2 and Go | Go, RE2, some WAFs | linear time guaranteed, but no backreferences and no lookaround in Go |
| .NET | C#, PowerShell | balancing groups, variable-length lookbehind |
| POSIX BRE and ERE | sed, awk, old grep | no \d, no lazy quantifiers, leftmost-longest matching |
# POSIX ERE: no shorthand classes, and + must be escaped in BRE
echo "abc123" | sed -E 's/[0-9]+/N/' # abcN
echo "abc123" | sed 's/[0-9]\+/N/' # abcN, BRE needs the backslash
# POSIX classes are the portable spelling inside brackets
grep -E '^[[:alpha:]]+[[:space:]]+[[:digit:]]+$' data.txt
# word boundary is not portable: \b is a GNU extension
grep -Ew 'cat' data.txt # -w is the portable intentLeftmost-longest is the POSIX rule: among matches starting at the earliest position, take the longest. Perl-style engines are leftmost-first and take the alternative written first, which is why the same alternation can produce different text in sed and in a browser.
RE2 and the linear-time promise
rejected by RE2 accepted by RE2
(a+)+b a+b
(\w+)\1 \w+\s+\w+
(?<=\$)\d+ \$\d+ (match the symbol, then skip it)
(?i)inline (?i)inline is fine in RE2
possessive a++ possessive is accepted, it has no cost to remove⚠️
RE2 trades features for a guarantee: no backtracking means no catastrophic blowup, at the price of no backreferences and limited lookaround. If a pattern must run on untrusted input behind a search endpoint, portability to RE2 is a security decision, not a style choice.
Writing portable patterns
- Prefer explicit classes:
[0-9]instead of\d,[A-Za-z0-9_]instead of\w. POSIX tools and some validators differ on the shorthands. - Avoid backreferences and lookaround when the pattern may run in Go or in a regex-based firewall; rewrite them with explicit alternatives.
- Do not rely on inline modifiers unless the target engine documents them. JavaScript has none, so a pattern copied from PCRE will throw at construction.
- State the engine in a comment next to the pattern. Most cross-engine bugs are not subtle: the pattern uses a construct the other engine simply does not have.
- Test in the least capable engine you support. A pattern validated only in PCRE routinely breaks in Go or in a JavaScript validator.
One practical consequence: a client-side pattern validated in JavaScript is not the same check the server performs in Python or Go. Validate at the boundary, in one place, with the engine that owns the data.
FAQ
Which engine should I learn first?
Perl-style semantics, because JavaScript, Python, PHP, Java and .NET all follow them. Learn POSIX separately if you write sed and awk, and treat RE2 as a constrained subset with a strong guarantee.
Why does my pattern fail in Go?
Go uses RE2 syntax, which has no backreferences and no lookaround. Replace them with explicit alternatives or do the second step in code after the match.
Related
Greedy, lazy and catastrophic backtracking Character classes, quantifiers and anchors
Last refreshed 2026-09-18.