Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Regex flavors compared

On this page
  1. At a glance
  2. JavaScript (ECMAScript)
  3. Python re
  4. Python regex (third-party)
  5. PCRE (Perl-Compatible Regular Expressions)
  6. RE2 (used by Go’s regexp, also a standalone library)
  7. .NET (C#, F#)
  8. POSIX BRE / ERE
  9. Porting patterns
    1. From PCRE to JavaScript
    2. From Python re to JavaScript
    3. From any flavor to RE2 (Go)
  10. Practical rules
  11. Try the tools

There is no single regex spec. Each language and engine has its own flavor with subtle differences. Here’s a side-by-side reference for the seven you’ll meet most often, and the practical rules for porting patterns between them.

At a glance

FeatureJSPython rePython regexPCRERE2 (Go).NETPOSIX ERE
Backreferences
Named captures
Lookahead
Lookbehindfixed-length
Atomic groups (?>…)
Possessive a++
Recursion (?R)
Conditional (?(1)…)
Unicode categories \p{...}with u flagvaries
Inline flags (?i)
Linear-time guaranteevaries

Two takeaways:

Most flavors fall between these poles.

JavaScript (ECMAScript)

What our tester uses. The default flavor for browser and Node code.

const re = /\b(\w+)@(\w+\.\w+)\b/gi;
const matches = [...text.matchAll(re)];

Has: lookarounds (all four directions, variable-length lookbehind since ES2018), named captures (?<name>...), Unicode mode u, sticky flag y, indices flag d.

Doesn’t have: atomic groups, possessive quantifiers, recursion, conditionals, inline flags. Older browsers may not support \p{...} even with u.

Use it for: browser code, Node servers, anything where regex runs against user input that could be unbounded.

Python re

The standard library. PCRE-ish.

import re
m = re.search(r"\b(\w+)@(\w+\.\w+)\b", text, re.IGNORECASE)
re.findall(r"\d+", text)

Has: named captures (?P<name>...) (note the P), inline flags (?i), fixed-length lookbehind, Unicode categories.

Doesn’t have: variable-length lookbehind, atomic groups, possessive quantifiers, recursion. Backreferences via \1 not $1.

Note: Python uses (?P<name>...) (with P) for named groups historically; modern Python 3.11+ also accepts the JS-style (?<name>...).

Python regex (third-party)

The regex package on PyPI is a near-superset of re plus PCRE features.

import regex
m = regex.search(r"(?P<word>\w+)@(?P=word)", text)  # backreference by name

Adds vs re: atomic groups, possessive quantifiers, variable-length lookbehind, fuzzy matching, set operations on character classes.

When PCRE features matter and you’re in Python, install regex.

PCRE (Perl-Compatible Regular Expressions)

The original “kitchen sink”. Used by grep -P, PHP’s preg_*, many editors’ “advanced” regex modes.

Has everything in this comparison. Recursion (?R), conditionals (?(1)yes|no), atomic groups, possessive quantifiers, callouts.

The cost: PCRE patterns can be exponential-time on adversarial input. For untrusted patterns or untrusted inputs, prefer RE2.

RE2 (used by Go’s regexp, also a standalone library)

Designed for guaranteed linear-time matching — the engine refuses to support features that could cause catastrophic backtracking.

re := regexp.MustCompile(`\b(\w+)@(\w+\.\w+)\b`)
matches := re.FindAllStringSubmatch(text, -1)

Has: named captures, Unicode categories, inline flags, all common quantifiers and character classes.

Doesn’t have: backreferences, lookaround. Period. By design.

Use it for: Go server code, security-sensitive matching (where adversarial input shouldn’t be able to DoS your regex engine), or any high-throughput pattern matching.

.NET (C#, F#)

Microsoft’s regex implementation. Surprisingly capable.

var re = new Regex(@"\b(?<user>\w+)@(?<domain>\w+\.\w+)\b", RegexOptions.IgnoreCase);
var match = re.Match(text);
match.Groups["user"].Value;

Has: variable-length lookbehind (since .NET 1.0!), named captures, balancing groups (a unique .NET feature for matching nested structures), right-to-left matching, conditionals.

Doesn’t have: PCRE recursion, possessive quantifiers (uses atomic groups instead).

POSIX BRE / ERE

The “ancient” regex flavors. Used by traditional Unix tools.

grep -E "\b\d{3}-\d{2}-\d{4}\b"   # ERE — modern grep
grep "\<[0-9]\{3\}\>"             # BRE — note backslashed parens/braces

Has: the basics — character classes, alternation, groups, anchors, quantifiers (different escaping rules in BRE vs ERE).

Doesn’t have: lookaround, backreferences (BRE has them, ERE technically doesn’t), named groups, Unicode categories. Limited character class support.

Use it for: shell scripting where portability matters more than features.

Porting patterns

From PCRE to JavaScript

Common adjustments:

(?>...)          → rewrite without atomic groups
a++              → rewrite without possessive quantifiers
(?R)             → recursion not available; use a parser
(?<=foo|bar)     → variable-length lookbehind works in JS (since ES2018)
\A, \z, \Z       → use ^ and $ instead
(?i)pattern      → use the i flag
\p{Greek}        → works with the u flag

From Python re to JavaScript

(?P<name>...)    → (?<name>...)
\1, \2           → backref syntax is the same in patterns
re.IGNORECASE    → /pattern/i
re.MULTILINE     → /pattern/m
re.DOTALL        → /pattern/s
re.UNICODE       → /pattern/u (now default in Python 3, opt-in in JS)

From any flavor to RE2 (Go)

The hard direction. RE2 doesn’t support:

If you can’t avoid these, you can’t use Go’s stdlib regex. Drop to a PCRE-via-cgo binding or use a different approach.

Practical rules

  1. Default to JavaScript regex if you’re writing a pattern for the web and need lookarounds/named captures.
  2. Default to RE2 / Go regex if you’re processing untrusted patterns or ultra-high-throughput data. The linear-time guarantee is real.
  3. Use PCRE / Python regex for one-off ETL or code analysis where you need every feature.
  4. Don’t write the regex twice for two flavors. Pick one canonical form and either port at build time or wrap it in language-specific regex objects.

Try the tools