Skip to content
Spellkit

Why the Same Regex Behaves Differently in JavaScript, Python, and grep

Regular expressions aren't one standard — they're a family of related-but-incompatible dialects, and the differences show up exactly when you copy a pattern between languages.

A regex that works perfectly in one tool and silently misbehaves in another isn't a fluke — "regex" isn't a single specification, it's a loose family of dialects that grew independently and never fully converged.

There's no single "regex" standard

Perl, PCRE (used by many languages' regex libraries), JavaScript's built-in RegExp, Python's re module, POSIX grep/egrep, and others all implement their own regex engines, each supporting a different subset of features with occasionally different syntax for the same idea. There's no governing spec that all of them implement — POSIX standardizes only a baseline, and everything past that (lookaheads, named groups, Unicode property escapes) is dialect-specific.

Named groups: three different syntaxes for the same feature

Capturing part of a match by name instead of by position is supported almost everywhere, but the syntax varies:

  • Python: (?P<year>\d{4})
  • JavaScript / PCRE / .NET: (?<year>\d{4})
  • Older POSIX tools: not supported at all — no named groups exist.

Copy a Python-style named group into JavaScript and it fails outright (JavaScript doesn't recognize the P in (?P<...>), rather than silently doing something close-but-wrong — an easy bug to trace, but only once you know the syntax difference exists.

Lookbehind: a feature that didn't exist everywhere until recently

Lookbehind assertions ((?<=...) for positive, (?<!...) for negative — match a position only if it is/isn't preceded by something, without consuming those characters) have an inconsistent history: PCRE and Python have long supported them, but JavaScript only added lookbehind support in relatively recent engine versions (ES2018), and older browsers' JS engines simply threw a syntax error on a pattern using it. A regex tested only in Python, then dropped into an older browser's JavaScript, could fail to even parse, not just match incorrectly.

Backreferences and recursion — supported unevenly

Backreferences (\1, referring back to an earlier captured group) are widely supported. Recursive patterns and fully backtracking-free matching, however, are not: Python's re doesn't support arbitrary recursion the way PCRE's does, and RE2-based engines (used in Go and some Google tools, and increasingly by browsers for performance/security) deliberately drop backreferences and lookaround entirely, trading some expressive power for a mathematical guarantee against catastrophic backtracking.

Character class quirks

\d, \w, and \s mean roughly "digit," "word character," and "whitespace" everywhere, but exactly which characters they match can differ — whether \w includes Unicode letters (accented characters, non-Latin scripts) or only ASCII [A-Za-z0-9_] depends on the engine and whether a Unicode flag is set. A pattern built and tested only against ASCII test data can quietly fail the moment real-world Unicode input (a name with an accent, non-English text) shows up.

Why "test it in the actual target language" matters

Because none of these differences raise a clear warning in the pattern itself — a lookbehind that doesn't exist in an engine is just a syntax error at best, or (worse, in a few historical engine quirks) silently interpreted as something else entirely — the only reliable way to know a regex works is to test it against the exact engine that will run it in production, not a different language's playground that happens to accept similar syntax.

Spellkit's Regex Tester runs your browser's own native JavaScript RegExp engine, so what you see matching here is exactly what RegExp will do in your actual JavaScript code — no separate implementation to introduce its own discrepancies.