Skip to content
Back to Blog
regexjavascriptpythoncomparison

JavaScript vs Python Regex: Key Differences

The same pattern can match in Python and quietly fail in JavaScript. This guide maps the real differences (missing string anchors, lookbehind support, replacement syntax) so you can write portable regex.

SZ
Founder, Molixa
13 min read
Share
JavaScript vs Python Regex: Key Differences
Table of contents12 sections

The biggest trap in javascript vs python regex is that the two engines share most of the same syntax, so a pattern looks portable until it silently misbehaves. JavaScript uses the ECMAScript flavor; Python uses its own re module flavor, closer to PCRE. They agree on the common 80%, then diverge on anchors, lookbehind, replacement strings, and Unicode. This guide tabulates every difference that actually breaks code so you can write patterns that survive the copy-paste between languages.

If you have ever lifted a regex off Stack Overflow and watched it match in one runtime but throw or return nothing in the other, this is why: the answer was written for one engine. Below is the full map, with examples and a quick way to test the same pattern in both flavors before you ship it.

JavaScript vs Python Regex at a Glance#

Here is the short version before the deep dive. These are the differences that cause real bugs, not trivia.

FeatureJavaScript (ECMAScript)Python (re)
String-start / string-end anchorsNone (^/$ only)\A and \Z
LookbehindFixed and variable (ES2018+)Fixed-width only
Possessive quantifiers (a++)Not supportedSupported (Python 3.11+)
Atomic groups (?>...)Not supportedSupported (Python 3.11+)
Named group syntax(?<name>...)(?P<name>...)
Named backreference\k<name>(?P=name)
Replacement group ref$1, $<name>\1, \g<name>
. matches newline flags (dotall)re.DOTALL / re.S
Unicode property escapes \p{...}Needs u or v flagNot built in (use regex module)
Inline flags (?i)Not supportedSupported
Default Unicode handlingUTF-16 code unitsUnicode by default in re

The rest of this article explains each row, why it matters, and how to rewrite a pattern so it works in both.

Quick tip: when a pattern works in one language and not the other, the cause is almost always anchors, lookbehind, or replacement syntax. Check those three first before assuming the regex itself is wrong.

Anchors: the \A and \Z Gap#

This is the single most common source of "works in Python, breaks in JavaScript" bugs. Python supports two anchor sets, JavaScript supports one.

In Python, ^ and $ match at the start and end of each line when the re.MULTILINE flag is on, while \A and \Z always match only the absolute start and end of the entire string regardless of flags. That distinction lets you write a strict whole-string validator that ignores multiline behavior.

JavaScript has no \A or \Z. It only gives you ^ and $, whose meaning flips based on the m (multiline) flag. So a Python pattern like \A\d{4}\Z has no direct JavaScript equivalent token.

// Python:    re.match(r"\A\d{4}\Z", text)
// JavaScript equivalent (no multiline flag, so ^ and $ are whole-string):
/^\d{4}$/.test(text);

The fix is to drop the m flag in JavaScript and rely on plain ^/$, which then anchor the whole string. The catch: JavaScript's $ can also match just before a trailing newline, so for true end-of-string strictness use $(?![\s\S]). Forgetting this is how multiline input sneaks past a validator that looked airtight.

Lookbehind: Fixed vs Variable Width#

Lookbehind is where the two flavors trade places, which surprises people who assume Python is always the more capable engine.

  • JavaScript added lookbehind in ES2018, and it supports both fixed and variable-length lookbehind. (?<=\$\d+) and (?<=cat|elephant) both work.
  • Python's built-in re module only supports fixed-width lookbehind. (?<=cat|dog) works because both alternatives are three letters, but (?<=cat|elephant) raises re.error: look-behind requires fixed-width pattern.

So a variable-length lookbehind that runs fine in a modern browser will throw at compile time in Python. If you need variable lookbehind in Python, install the third-party regex module, which supports it, or restructure the pattern to use a capture group instead of a lookbehind.

# Fails in Python's re:
re.compile(r"(?<=USD |EUR )\d+")   # different lengths -> error
# Workaround: capture and reference, or pad alternatives to equal width
re.compile(r"(?:USD|EUR) (\d+)")   # capture the number in group 1

If you are still shaky on how lookbehind and lookahead actually behave, the deeper mechanics are worth a read in our regex lookahead and lookbehind guide before you port patterns between languages.

Capture Group and Backreference Syntax#

Both engines support named groups, but the syntax differs, and this is a hard compile error rather than a silent mismatch.

JavaScript uses (?<name>...). Python uses (?P<name>...) with that extra P. Get them crossed and the pattern simply will not compile.

ActionJavaScriptPython
Define named group(?<year>\d{4})(?P<year>\d{4})
Backreference inside pattern\k<year>(?P=year) or \1
Numbered backreference\1\1

Numbered backreferences (\1, \2) are identical in both, so they are the safest choice when you want one pattern string to run unchanged in both languages. The moment you reach for named groups, you must branch your pattern per engine.

Inline flags#

Python lets you set flags inside the pattern with (?i) for case-insensitive, (?m) for multiline, and so on, including the scoped form (?i:...). JavaScript historically did not support inline flag modifiers at all; flags went only on the literal (/pattern/i) or the RegExp constructor. Scoped inline modifiers (?i:...) are a newer ECMAScript addition and are not yet universal across runtimes, so do not rely on them for portable code.

Replacement String Syntax (the Silent Bug)#

This one is dangerous because it does not throw. It just produces wrong output, which is far harder to catch in review than a crash.

When you reference a captured group in a replacement string, JavaScript uses the dollar sign and Python uses a backslash.

// JavaScript: $1 references group 1, $<name> references a named group
"2026-06-25".replace(/(\d{4})-(\d{2})/, "$2/$1");  // "06/2026-25"
# Python: \1 references group 1, \g<name> references a named group
re.sub(r"(\d{4})-(\d{2})", r"\2/\1", "2026-06-25")  # "06/2026-25"

If you paste a JavaScript replacement string with $1 into Python's re.sub, Python treats $1 as the literal characters dollar-one, and your replacement quietly inserts $1 into the output instead of the captured value. No error, just corrupted text. The reverse happens too: a \1 in a JavaScript replacement is read as an escape and breaks.

Warning: replacement-string syntax errors do not raise exceptions. Always test a substitution against a real sample string in the target language, not just the match pattern.

The Dotall Flag and Multiline Matching#

By default, . does not match a newline in either engine. To change that, the flag names differ.

  • JavaScript: the s flag (introduced in ES2018), as in /foo.bar/s.
  • Python: re.DOTALL or its short form re.S, passed to re.compile or the function call.

The behavior once enabled is identical, but JavaScript attaches flags as letters after the closing slash or as the second RegExp argument, while Python passes flag constants combined with the bitwise OR operator, like re.DOTALL | re.IGNORECASE.

A portable habit: avoid relying on . to cross lines at all. Use the explicit class [\s\S] in either language, which matches any character including newlines without any flag. That single trick removes one of the most common cross-language flag mismatches.

Unicode: Property Escapes and Default Behavior#

Unicode is where the engines diverge most quietly, and it bites hardest on non-ASCII input.

JavaScript treats strings as UTF-16 code units by default. Without the u (Unicode) flag, an astral character like an emoji is two code units, and . or a quantifier can match half of it, producing mojibake. Adding the u flag makes the regex operate on code points and unlocks Unicode property escapes like \p{Letter} and \p{Script=Greek}. The newer v flag extends this further with set operations.

Python 3 strings are Unicode by default, so \w and \d already match Unicode letters and digits unless you pass re.ASCII. However, Python's built-in re module does not support \p{...} property escapes at all. If you need property escapes in Python, you install the third-party regex module, which adds them.

NeedJavaScriptPython re
Match by Unicode property \p{L}u or v flagNot supported (use regex module)
Code-point-correct matchingRequires u flagDefault
ASCII-only word charsDefault without u is partialre.ASCII flag

The practical takeaway: in JavaScript, set the u flag whenever your input might contain non-ASCII text. In Python, expect Unicode-aware \w/\d by default, but do not reach for \p{...} unless you have the regex library installed.

Possessive Quantifiers and Atomic Groups#

These are backtracking-control features. Possessive quantifiers (a++, a*+, a?+) and atomic groups ((?>...)) stop the engine from backtracking into a matched section, which can prevent catastrophic backtracking on adversarial input. Python's built-in re added both in Python 3.11. JavaScript's ECMAScript regex supports neither.

So a pattern using (?>...) or a++ from a Python 3.11+ codebase throws a syntax error in JavaScript. To get similar control in JavaScript, you rewrite the pattern with a lookahead-plus-backreference trick (harder to read) or restructure the regex to be unambiguous in the first place.

How to Test the Same Pattern in Both Flavors#

The reliable way to avoid all of these traps is to test the exact pattern against real sample text in each flavor before you commit it. Reading a compatibility table tells you what should happen; running the pattern tells you what does happen.

Step 1: Write the pattern once and define your test strings#

Start with the pattern and three to five sample inputs that cover your edge cases: a clean match, a near-miss, a multiline case, and a non-ASCII case if relevant. Keep them in front of you so you can confirm matches by eye.

Step 2: Run it in the JavaScript flavor#

Paste the pattern and samples into a free multi-flavor regex tester set to the JavaScript engine. Confirm the matches, capture groups, and any replacement output. Watch specifically for anchor behavior and Unicode handling here.

Step 3: Switch the flavor to Python and re-run#

Change the engine to Python and run the identical pattern and samples again. Compare match counts and captured groups against the JavaScript result. A difference here is your portability bug, surfaced before it reaches production.

Step 4: Fix the divergence using the tables above#

When the results differ, map the symptom to the relevant section: anchors, lookbehind, named-group syntax, replacement string, or Unicode flags. Apply the portable rewrite (numbered groups, [\s\S] instead of dotall, equal-width lookbehind) and re-run both until they agree.

If part of the pattern is doing something you cannot decode, pasting it into a plain-English code explainer can break down each token and flag the engine-specific pieces, which speeds up the fix.

Writing Portable Regex: A Short Checklist#

When a pattern must run in both JavaScript and Python without branching, stick to the common subset:

  • Use numbered groups and \1 backreferences, not named groups.
  • Use [\s\S] instead of relying on the dotall flag to cross newlines.
  • Keep lookbehinds fixed-width so Python's re accepts them.
  • Avoid possessive quantifiers and atomic groups.
  • Avoid \A/\Z and \p{...} property escapes.
  • Test substitutions separately, since $1 vs \1 is a silent failure.

For data-shaping work where you also need to inspect structured output alongside your matches, pairing the tester with a JSON formatter keeps the match results readable while you debug.

Conclusion#

The javascript vs python regex divide is rarely about the syntax you can see; it is about the four quiet differences that change behavior without raising an error: missing \A/\Z anchors, fixed-width-only lookbehind in Python, the $1 versus \1 replacement split, and Unicode property-escape support. Memorize those four, default to the portable subset when a pattern crosses languages, and test the exact pattern in both engines before you trust it. That habit turns regex from a copy-paste gamble into something you can move safely between your front-end and your back-end.

Frequently Asked Questions#

Why does my regex work in Python but not JavaScript? The most common causes are Python-only features in your pattern: \A/\Z anchors, variable-length lookbehind, possessive quantifiers, atomic groups, or (?P<name>...) named groups. JavaScript's ECMAScript flavor does not support these, so the pattern either throws a syntax error or quietly matches nothing. Rewrite using the portable subset (numbered groups, fixed-width lookbehind, plain ^/$) and test in both engines.

Does JavaScript support lookbehind like Python? Yes, and in one respect JavaScript is more capable. Since ES2018, JavaScript supports both fixed-width and variable-length lookbehind, while Python's built-in re module only supports fixed-width lookbehind. A variable-length lookbehind such as (?<=cat|elephant) runs in modern JavaScript but raises an error in Python's re. For variable lookbehind in Python, use the third-party regex module.

What is the difference between $1 and \1 in regex replacements? They reference the same captured group, but each belongs to a different engine. JavaScript replacement strings use $1 (and $<name> for named groups), while Python's re.sub uses \1 (and \g<name>). Mixing them does not raise an error; it inserts the literal characters instead, silently corrupting output. Always test substitutions against a real sample string in the target language.

Are JavaScript and Python regex flavors compatible? They share most syntax, roughly the common 80%, including character classes, quantifiers, alternation, and numbered backreferences. They diverge on anchors, lookbehind width, named-group syntax, replacement strings, inline flags, possessive quantifiers, and Unicode property escapes. A pattern restricted to the shared subset will run unchanged in both; anything beyond it needs per-engine branching or a rewrite.

How do I match Unicode characters by property in each language? In JavaScript, enable the u (or v) flag and use property escapes like \p{Letter} or \p{Script=Greek}. Python's built-in re module does not support \p{...} at all, so you install the third-party regex module to get property escapes. By default, Python 3's re already makes \w and \d Unicode-aware, whereas JavaScript needs the u flag for code-point-correct matching.

What is the fastest way to find the difference between two regex flavors? Run the exact pattern and the same set of sample strings through a multi-flavor regex tester, switching the engine from JavaScript to Python and comparing match counts and captured groups. Any difference in the output is your portability bug, isolated before it ships. This is faster and more reliable than reasoning about compatibility tables alone, because it shows actual behavior on your real input.

regexjavascriptpythoncomparison

More from Molixa

Try Molixa Tools

50+ free AI tools for content creation, SEO, coding, and more. No signup, no watermark.

Explore all tools
JavaScript vs Python Regex Differences | Molixa