Published July 2026 · 8 min read

Regex flavors,
and where they differ.

A pattern that works in your editor and fails in your code is usually not a typo. Regex is not one language. The engines disagree about lookbehind, about how you name a group, about what a dot matches, and most importantly about whether a bad pattern can hang the process.

JS lookbehind since ES2018 RE2 no backtracking Unicode classes needs the u flag Biggest risk catastrophic backtracking

The engines you will meet

EngineUsed byBacktracking
JavaScriptBrowsers, NodeYes
PCREPHP, nginx, many CLI toolsYes
Python rePythonYes
RE2Go, some Rust, BigQueryNo

That last column matters more than any syntax difference. A backtracking engine can take exponential time on a hostile input. RE2 cannot, because it refuses the features that make it possible.

Named groups

Three syntaxes for the same idea:

JavaScript, .NET   (?<year>\d{4})
Python             (?P<year>\d{4})
PCRE               both of the above work

JavaScript reads them back from match.groups.year, Python from m.group("year"). Copying a Python pattern into JavaScript fails on the P.

Lookbehind

Lookahead is universal. Lookbehind is not. JavaScript gained it in ES2018 and supports it in variable length form, which is unusually generous. PCRE and Python both require lookbehind to be fixed length, so (?<=ab|cde) is an error in Python and legal in JavaScript. RE2 has no lookbehind at all.

If a pattern has to run in more than one engine, avoid lookbehind. It is the single most common portability failure.

Unicode, and the u flag

In JavaScript, \p{...} property escapes only work with the u flag. Without it the pattern either throws or, worse, silently treats \p as a literal p.

/\p{Script=Greek}/u    matches Greek letters
/\p{Script=Greek}/     matches "p{Script=Greek}"

The u flag also changes what a dot and a character class match: with it, surrogate pairs are treated as single code points, so emoji and other astral characters behave. Turn it on unless you have a reason not to.

Flags that mean different things

The m flag makes ^ and $ match at line breaks. The s flag, called DOTALL in Python, makes the dot match a newline. People routinely reach for m when they want s.

In JavaScript the g flag also makes the regex object stateful. It keeps a lastIndex between calls, so reusing one global regex across several tests gives alternating results. This is a genuine trap:

const re = /a/g;
re.test("a");  // true
re.test("a");  // false, lastIndex moved

Catastrophic backtracking

Nested quantifiers over overlapping alternatives are the classic hazard. A pattern like (a+)+b against a long run of a with no b forces the engine to try every partition of the string, which is exponential.

The fix is not a faster machine. Make the alternatives non overlapping, anchor the pattern, use a possessive quantifier or atomic group where the engine supports them, or move to RE2 where the failure mode does not exist. If the input comes from a user, treat it as a denial of service risk and not a performance question.

A portability checklist