Open data · MIT

Where regex engines disagree

You copy a regex from Stack Overflow, try it on a regex-testing site, and paste it into your code. In your code it quietly does something different. It doesn’t crash. It doesn’t warn. It just returns a different answer — because every programming language has its own regex engine, and they don’t agree with each other nearly as often as people assume. We measured how often. This page is the short version; the full data is on GitHub.

Does a regex behave the same in every language?

No — and when it doesn’t, you usually get no warning. Here’s an everyday example. ^.+$ with the multiline flag means “give me every line”. Run it on three lines of text that came out of a Windows file — where lines end in \r\n instead of just \n — and here is what the same pattern returns in different languages:

What you get backLanguages
Lines with an invisible \r stuck to the endPython, C#, Go, Perl, PHP, R, Rust, Elixir
Clean linesJavaScript, Dart
The whole text as one big matchRuby
An error — pattern refusedPostgreSQL

Four different outcomes from one pattern. If you built it in a browser console and shipped it in Python, every value you extract now ends in an invisible carriage return — the kind of bug that surfaces weeks later as “these two identical-looking strings don’t compare equal”. Ruby returns everything as one match because Ruby’s m flag doesn’t mean “multiline” at all — it means “dot matches newlines” (Ruby’s ^ and $ already work per-line). Same letter, different meaning. And PostgreSQL simply refuses the pattern. At no point did anything warn anyone.

How often do regex engines disagree?

To put a number on it, we wrote 539 small test patterns — one per regex feature, edge case or known quirk — and ran each of them on the real engines of 16 languages: the actual CPython, the actual Ruby interpreter, a real JVM, real .NET, real PostgreSQL. For the 534 patterns that ran on more than one engine, here is the outcome:

OutcomePatternsWhat that means for you
Everyone agrees270Same matches in every language. All good.
Different answers, silently21Two languages both “work” — and return different matches.
Nothing matches, silently160A valid pattern finds nothing in at least one language. No error.
Rejected, loudly83At least one language refuses the pattern with an error.

The loud failures are the good ones — your code breaks, you notice, you fix it. The scary rows are the middle two: 181 patterns where a language hands you a wrong answer and nothing, anywhere, tells you. That is one pattern in three.

What is the worst single example?

Two characters: \h. In Ruby, \h means “a hex digit” — 0–9 and a–f. In Perl and PHP, the very same \h means “a horizontal space”. Not slightly different — opposite. And both sides happily return matches:

PatternTextRuby findsPerl / PHP find
\h+DEAD 00FF xyzthe hex numbers: “DEAD”, “00FF”the two spaces between them

A close runner-up: \p{L}+ means “one or more letters, in any alphabet”. Run it on Hello 世界 café and nine languages find the three words. JavaScript finds nothing — no error, zero matches — because without the u flag it reads \p as a plain letter p. The fix is one character. Nothing tells you that you need it.

How was this measured?

By running the patterns for real — not by reading documentation, and not by simulating one engine inside another. Every answer in the dataset came out of the actual language runtime: CPython for Python, MRI for Ruby, a GraalVM-built JVM for Java, .NET for C#, PCRE2 for PHP, PostgreSQL’s own engine, and so on. Languages we couldn’t genuinely execute were left out rather than guessed at — an earlier assumption that Dart behaves like JavaScript turned out wrong in 5 cases once Dart was actually run, which is exactly why guessing is banned. The test patterns, the raw per-language results and the scripts that produced them are all public on GitHub under the MIT license, and the report rebuilds from the raw data with one command.

What should you do about it?

Test your regex on the engine you’ll actually ship it to — not on whatever engine your browser or your favourite regex site happens to use. That’s the whole reason RegexPilot bundles the real engines of 21 languages and runs your pattern through the one you target: the answer you see while building is the answer production will give.