Python regex vs JavaScript regex — 7 differences that matter
They look almost identical. They differ in seven specific ways that will absolutely bite you when porting code.
The summary
| Feature | JavaScript | Python (stdlib re) |
|---|---|---|
| Named group syntax | (?<n>...) | (?P<n>...) |
| Named backreference | \k<n> | (?P=n) |
| Variable-width lookbehind | ✓ since ES2018 | ✗ fixed-width only |
| String literal escaping | Backslashes doubled in strings | Use raw strings r"..." |
| Absolute anchors \A \z | ✗ | ✓ |
| Default Unicode behavior | ASCII unless /u | Unicode in Python 3 |
| Possessive quantifiers | ✗ | 3.11+ |
1. Named groups have different syntax
JS: (?<year>\d{4})
Python: (?P<year>\d{4})
The Python syntax with the P comes from the original Python regex module. Other modern engines (PCRE, .NET, JS) adopted the cleaner (?<name>...) later. PCRE supports both.
When porting Python to JS, search-replace (?P< with (?<. The flavor converter does this automatically.
2. Named backreferences are different too
JS: \k<name>
Python: (?P=name)
Same naming convention difference. Both flavors recognize numbered backrefs (\1, \2) identically.
3. Python's stdlib lookbehind requires fixed width
This is the biggest gotcha when porting JS regex to Python:
JS (works): (?<=\$+)\d+
Python stdlib: re.error: look-behind requires fixed-width pattern
Python's standard re module needs every alternative in a lookbehind to have the same length. JavaScript handles variable-width since ES2018.
Workarounds:
- Use the third-party
regexmodule (pip install regex) - Restructure:
\$+(\d+)and use the capture group
4. String escaping is wildly different
In JavaScript, regex literals use slashes /.../:
const r = /\d+/; // clean, single backslashes
const s = "\\d+"; // string form needs doubled backslashes
In Python, you almost always use raw strings:
r = re.compile(r"\d+") # raw string — single backslash
r = re.compile("\\d+") # without r prefix, need to double
Forgetting the r prefix in Python is a major source of bugs — "\n" in a non-raw string is a literal newline, not the regex newline metacharacter.
5. \A, \Z, \z — Python has them, JavaScript doesn't
In Python (and PCRE), you can match the absolute start or end of input regardless of multiline mode:
Python: \A start of string (always)
\Z end of string (always, or before trailing newline)
\z end of string (always, no exceptions)
JavaScript doesn't have these. Use ^ and $ — but be aware they match line boundaries when the m flag is on.
6. Unicode behavior differs
By default:
- JavaScript:
\dis ASCII[0-9]; adduflag for Unicode - Python 3:
\dmatches Unicode digits by default; addre.ASCIIfor ASCII-only
This affects which digits and letters are recognized — Arabic numerals, Devanagari digits, etc. If your text might be non-ASCII, decide which behavior you want explicitly.
7. Possessive quantifiers
Possessive quantifiers (*+, ++, ?+) eliminate backtracking. They're supported in:
- Python: 3.11+ only
- JavaScript: not at all (use atomic groups via lookaheads as a workaround)
- PCRE / Java / Ruby: always
Beyond the basics
A few subtler differences:
- Replacement syntax: JS uses
$1, Python uses\1(or\g<name>) - Compilation: Python explicitly compiles (
re.compile); JS compiles lazily on use - Sticky flag: JS has the
yflag for sticky matching at lastIndex; Python doesn't - matchAll: JS has
matchAll; Python hasfinditer(similar idea)
The takeaway
The two flavors look almost identical and disagree in small but important ways. When porting a regex, watch especially for: named group syntax, lookbehind width restrictions, raw-string usage, and Unicode behavior.
If you regularly move regex between Python and JavaScript, bookmark the flavor converter — it handles the syntactic differences automatically and flags features that don't carry over.
Related reading
Try this pattern in the explainer
Paste any regex into the live explainer and see what each token means, with example matches in real time.
Open the regex explainer →