Which regex flavour does this test?+
The one already running in your browser. There is no engine bundled into this page: your pattern is handed to the JavaScript runtime's own RegExp constructor and the matches you see are the matches your code will get. That makes the results exactly right for JavaScript, TypeScript, Node, Deno, and Bun. For anything else they are only approximately right. Python's re, PCRE in PHP and Perl, Go's RE2, Rust's regex crate, .NET, Java, grep, sed, and ripgrep all differ, sometimes in syntax and sometimes in what the same syntax means. If the pattern is destined for a different language, test it there before shipping it.
What is the difference between greedy and lazy quantifiers?+
A greedy quantifier takes as much as it possibly can and then gives characters back one at a time until the rest of the pattern fits. A lazy one (written by adding a question mark, so *? +? ?? {n,m}?) takes as little as it can and grows only when forced. The classic demonstration is /<.+>/ against “<a><b>”: the greedy .+ swallows everything to the end, backtracks to the last “>”, and returns “<a><b>” as a single match. Change it to /<.+?>/ and you get “<a>”, then “<b>”. Neither is more correct; they answer different questions. The practical rule is that greedy is the right default when the delimiter is unique and lazy is the right default when it repeats.
What is catastrophic backtracking?+
It is what happens when a pattern can split the same text many different ways and the match ultimately fails. JavaScript matches by backtracking, so on failure it retries every remaining combination before giving up. The textbook trigger is a repeat inside a repeat, such as /(a+)+$/ against a long run of “a” followed by one “b”. There are exponentially many ways to divide 30 a's into groups of one or more, and the engine tries all of them before concluding the “$” cannot match. Thirty characters take milliseconds; forty take about a thousand times longer. The fixes are to make the inner and outer repeats unable to match the same text, to replace a quantified group with a character class, or to anchor the pattern so failure is detected early. This page scans your pattern for those shapes and names them before you run it.
Why did lookbehind arrive so late in JavaScript?+
Lookahead was in the language from the beginning, but lookbehind only landed in ES2018 (more than twenty years later), and until Safari shipped it in 2023 a pattern using it threw a syntax error on a browser many sites still supported. The delay was partly the general slowdown in language evolution between ES4's abandonment and ES6, and partly that lookbehind is genuinely harder to implement: the engine has to match backwards from the current position. JavaScript's implementation ended up better than most, because unlike PCRE, Python's re, and Java it allows variable-length lookbehind. /(?<=\$\d+ )item/ is legal here and rejected outright in those. If a pattern of yours needs to run in an old environment, the traditional workaround is to capture the preceding context in a group and discard it afterwards.
What is the difference between the u and v flags?+
The u flag, from ES2015, puts the pattern into Unicode mode: astral characters such as emoji count as one unit rather than two UTF-16 halves, \u{1F600} becomes valid, \p{…} property escapes are unlocked, and escapes with no meaning become syntax errors instead of being quietly ignored. The v flag, from ES2024, is a superset that adds set notation inside character classes (difference with --, intersection with &&, and multi-character string properties), so you can write [\p{Letter}--[a-z]] to mean “any letter except an ASCII lowercase one”. The two flags are mutually exclusive: a regex is either u-mode or v-mode, never both. This page offers u and not v, because a pair of buttons that silently cancel each other is a worse interface than one that is honest about its scope.
Why does my global regex skip every other match?+
Because a regex carrying the g or y flag is stateful. It stores a lastIndex property, and exec and test both start from there and update it on success. Store one in a module-level constant, call test twice on the same string, and the second call starts halfway through and returns false. The same bug bites when a global regex is reused across items in a loop. There are three ways out: build the regex fresh where it is used, reset lastIndex to 0 before each call, or use matchAll and String.match, which do not leave the cursor where you can trip over it. This page sidesteps the problem entirely by compiling a private working copy for every run, so the regex you are looking at is never the one being mutated.
What can PCRE do that JavaScript regex cannot?+
Several things, and knowing which ones saves a lot of confused debugging when you copy a pattern across. There are no atomic groups: PCRE's (?>…) commits to a match and refuses to backtrack into it, which is the standard hand-fix for catastrophic backtracking, and JavaScript's only substitute is a lookahead wrapping a capture. There are no possessive quantifiers (a*+) for the same reason. There is no recursion (?R) and there are no subroutine calls, so balanced brackets are out of reach. There are no inline modifiers: (?i) mid-pattern is a syntax error, because a JavaScript flag applies to the whole regex or not at all. There are no conditionals, no \A \z \Z anchors, no POSIX classes such as [[:alpha:]], and no comment mode. What JavaScript does have that many engines do not is variable-length lookbehind.
Does \d mean the same thing everywhere?+
No, and the difference is a real source of security bugs. In JavaScript \d is exactly [0-9], always, with or without the u flag. In .NET and Python 3, \d matches any character with the Unicode decimal-digit property by default, which includes Arabic-Indic digits ٠١٢, Devanagari digits, and fullwidth 012. A validator written in Python that accepts ٢٠٢٦ and a JavaScript one that rejects it will disagree about the same input, which is precisely the sort of mismatch that gets exploited. The same caution applies to \w, which is [A-Za-z0-9_] here and so does not consider “é” a word character, and to \b, which is defined in terms of \w and therefore lands in the middle of “naïve”. If you want Unicode semantics in JavaScript you have to ask for them explicitly: \p{Nd} with the u flag for digits, \p{L} for letters.
When should I stop using a regular expression?+
When the thing you are matching can nest. Regular expressions in the formal sense cannot count, and although backreferences push JavaScript's engine past a strictly regular language, it still cannot match balanced structure. That rules out HTML, XML, JSON, source code, and any bracket language, not because the pattern is hard to write but because no pattern exists. Use DOMParser, JSON.parse, or a real parser. Three softer signals point the same way: the pattern has grown past a line or two and nobody can read it, it needs a comment to explain each clause, or it keeps acquiring special cases for inputs it was supposed to already handle. A regular expression is at its best finding and validating flat, well-shaped fragments (a date, a hex colour, a log prefix), and at its worst pretending to be a grammar.
What do $1, $&, $` and $' mean in a replacement?+
They are the substitution patterns String.replace understands in the replacement string. $1 through $99 insert the text of that numbered capture group, and $<name> does the same for a named one. $& inserts the whole match. $` inserts everything before the match and $' inserts everything after it; both are surprisingly useful for wrapping and surprisingly easy to trigger by accident, since a backslash does not escape them. $$ inserts a single literal dollar sign. Two rules catch people out: a reference to a group that does not exist is left in the output as plain text rather than throwing, and $12 means group 12 if the pattern has twelve groups and group 1 followed by the character “2” if it does not. This page expands all of them in the preview and flags any reference it could not resolve.