Which engine this is, and why that matters
This runs ECMAScript regular expressions, because it is your browser's own RegExp doing the work. A pattern that behaves a certain way here behaves the same way in JavaScript, TypeScript and Node. It does not follow that it behaves the same way anywhere else. PCRE — what PHP, grep -P and most "regex" in shell tooling actually use — has possessive quantifiers, atomic groups, recursion and \A/\z anchors that ECMAScript simply does not have. POSIX ERE, which plain grep -E and sed -E use, has no lazy quantifiers, no \d, no lookaround at all, and applies leftmost-longest matching rather than leftmost-first, so an alternation can pick a different branch than it does here. Go's RE2 and Rust's regex crate drop lookaround and backreferences on purpose to guarantee linear time.
The practical rule: test here for anything that will end up in JavaScript, and treat the result as a strong hint rather than a guarantee for anything else. The features most likely to break on the way out are lookbehind, named groups, \p{...} Unicode property escapes and the s (dotAll) flag.
What each flag changes
| Flag | Effect | The gotcha |
|---|---|---|
| g | Find every match instead of stopping at the first | replace() without g changes one occurrence, which is the single most common "why did only one change" bug |
| i | Case insensitive | Case folding is per-character; it does not help with accents or width variants |
| m | ^ and $ anchor to each line | Without it, $ means end of the whole string, so line-oriented log patterns silently match nothing |
| s | . also matches a newline | Without it a multi-line block cannot be captured by .* no matter how greedy |
| u | Code-point semantics, enables \p{...} | Escapes that were harmlessly redundant without u become syntax errors with it, so adding u can break a pattern that worked yesterday |
Replacement strings are not patterns
The replacement field is a template, not a regex. $1 and $2 insert capture groups, $& inserts the whole match, $<name> inserts a named group, and $$ gives you a literal dollar sign. Python's \1 backreference syntax does nothing here — it inserts the character 1 preceded by nothing useful. Tick the replace checkbox with the field left empty to see what deleting every match looks like, which is a different thing from leaving the field blank and getting no preview at all.
Catastrophic backtracking, and the guard-rails on this page
A pattern like (a+)+b against a long run of a characters makes the engine try an exponential number of ways to split the input before it admits defeat. Nested quantifiers over overlapping character sets are the usual shape, and a quantified alternation whose branches start with the same character is the other one. On a server this is a denial-of-service bug with a CVE class of its own; in a browser tab it is a frozen page, because a single exec() call runs to completion and JavaScript has no way to interrupt it from the outside.
Capping the input does not save you here, and it is worth being clear about why. Exponential means (a+)+b against forty characters is already in the billions of steps. There is no input length that is both large enough to be useful for ordinary patterns and small enough to be safe for this one. So this page does two separate things. It scans the pattern first and refuses to run it if it finds either of those two shapes, naming the construct it objected to. Everything that gets past that scan is then bounded anyway: the test string is truncated to 2,000 characters, patterns over 1,000 characters are rejected, the match loop stops at 2,000 matches, and a 400 ms budget is checked between matches.
Polynomial blowup is the milder cousin and gets a graded treatment. Each unbounded wildcard — .*, .+, a negated class with a star — adds roughly one power to the worst-case step count, so a.*a.*b is cubic and a.*a.*a.*b is quartic. Cubic at two thousand characters takes seconds, which is why the input cap is not a single number: the tool counts the wildcards and scales the cap down as they multiply — two thousand characters for zero or one, three hundred for two, eighty for three, and a refusal at four or more, where no test string is both long enough to be useful and short enough to finish. The output tells you when it has shortened your input and why.
None of this is a proof, only a set of heuristics for the shapes that actually turn up. It will occasionally refuse something that would have been fine. When it does, the rewrite it is asking for is usually the change you wanted anyway: (a+)+b is just a+b, and (\s|\t)* is just [\s\t]*.
Questions people ask
Why does my pattern only find one match?
The g flag is off. Without it the engine stops at the first match, and replace() rewrites only that one occurrence. This is the most frequent regex bug in JavaScript by a wide margin, partly because the symptom — "it worked on my test input, which had one match" — looks like success.
Is my pattern or test string sent anywhere?
No. The pattern is compiled with new RegExp() and matched inside the page. There is no network request involved in running it, which you can confirm in the network tab of your dev tools. Nothing is stored either — reload the page and the fields go back to the defaults.
An emoji counts as two characters in the offsets.
Offsets are UTF-16 code units, which is what JavaScript string indices are, and most emoji are surrogate pairs occupying two of them. Turning on the u flag makes the pattern treat a surrogate pair as one code point for matching purposes, so a dot will consume the whole emoji, but the reported index stays in code units. Anything outside the BMP will show this, not just emoji.
It refused to run my pattern. Is the pattern wrong?
Not necessarily wrong, but it has a shape that can backtrack exponentially: a quantifier inside a group that is itself quantified, or a quantified alternation whose branches can begin with the same character. The refusal is blunt because there is no safe input size for those — a single exec() call cannot be interrupted, so running it would freeze the tab rather than time out. The scan is a heuristic and does produce occasional false positives, but the rewrite it points at is normally the change you wanted regardless: collapse the nested quantifier, or replace the overlapping alternation with a character class.
Can I paste a pattern copied from Python or PHP?
Usually, but check three things. Drop any surrounding delimiters and trailing flag letters — write the flags with the checkboxes instead. Replace Python raw-string escapes only if the source string was not raw. And if the pattern uses possessive quantifiers, atomic groups, recursion or conditionals, ECMAScript has none of those and will reject it outright rather than silently doing something different.