A regular expression is a tiny program, and like any program it is easier to fix when you can watch it run. Type a pattern, paste some text, and the matches light up as you go. Everything here uses JavaScript's regex engine — which matters, because regex flavours differ more than people expect.
Some patterns can hang for hours, so this one runs in a worker
JavaScript's regex engine backtracks, and backtracking can explode. The textbook example is (a+)+b: against a string of thirty "a" characters with no "b", the engine tries every way of splitting those thirty characters between the inner and outer quantifier — over a billion paths — before concluding there is no match. Add five more characters and it takes thirty times longer.
This is not theoretical. Run that pattern in a naive tester and the browser tab locks solid, because a running regex cannot be interrupted from inside JavaScript. This tool therefore evaluates your pattern in a Web Worker, on a separate thread, with a one-second budget. If the pattern blows past it, the worker is killed and you get told the pattern is pathological instead of watching the page die. The tab stays responsive the whole time.
The lesson generalises well beyond this page: if user-supplied patterns ever reach a regex engine on your server, that engine can be hung the same way. It has a name — ReDoS — and it has taken down real services.
The g flag makes your regex remember things
This is the JavaScript regex bug that costs the most debugging hours, and it looks like magic when it hits you. A regex object with the g flag keeps a lastIndex property, and .test() and .exec() both advance it. So calling the same test twice on the same string gives different answers.
Try it in a console: const re = /a/g; re.test("a") returns true, then re.test("a") returns false, then true again. It alternates forever. The first call matches at position 0 and sets lastIndex to 1; the second starts searching from position 1, finds nothing, and resets lastIndex to 0. Nothing is broken — the regex is behaving exactly as specified, and the specification is surprising.
The practical rules: never store a g-flagged regex in a module-level constant and reuse it with .test(). If you only want a yes/no answer, drop the g. If you need one, build the regex fresh at each use, or reset lastIndex to 0 yourself. This tool sidesteps the whole thing by constructing a new regex for every keystroke.
JavaScript is not PCRE
A pattern lifted from a Perl, PHP or Python answer will often work here, and occasionally it will not, in ways that are easy to miss. JavaScript has no atomic groups and no possessive quantifiers — the two features other flavours give you specifically to prevent the backtracking explosion described above. It has no recursion, so the classic "match balanced parentheses" patterns are simply impossible.
Lookbehind does exist in JavaScript, and has since ES2018, but it arrived late: Safari only shipped it in version 16.4 in March 2023. If you support older iOS devices, a pattern using (?<=...) will not fail to match — it will throw a SyntaxError when the regex is constructed, taking your script down with it. That is a much noisier failure than a missing match, and worth knowing before you ship one.
The flags, briefly
g finds every match rather than stopping at the first. i ignores case. m changes what ^ and $ mean — they match at line boundaries instead of only at the start and end of the whole string, which is what people usually want when testing against pasted multi-line text. s makes the dot match newlines too; without it, a dot will not cross a line break, which explains a surprising number of "why does my pattern stop at the end of the line" questions.
u turns on proper Unicode handling. Without it, the engine works in UTF-16 code units, so a single emoji counts as two characters and a character class can slice it in half. If your text has anything beyond the basic multilingual plane, you want u.
When not to use a regex
Regex matches patterns in flat text. It cannot count, it cannot remember arbitrary nesting, and it has no concept of structure. HTML, JSON and source code all nest, which is why every attempt to parse them with a regex works on the examples and fails on the real data. Use a parser; every language has one.
Email addresses deserve their own warning. The grammar in RFC 5322 permits comments, quoted strings and nested constructs, and the regexes that implement it faithfully run to thousands of characters. Meanwhile a valid address can still bounce, and an "invalid-looking" one can be perfectly deliverable. Check that there is an @ with something either side, then send a confirmation email — that is the only test that actually proves anything.
Common questions
Is my pattern or text sent to a server?
No. This tool is marked "client": the pattern runs in a Web Worker inside your own browser, which is a separate thread on your machine and not a remote one. Nothing is transmitted, and nothing is stored.
Why did I get "This pattern is taking too long"?
Your pattern exceeded the one-second budget, which in practice always means catastrophic backtracking rather than a genuinely big job. Look for nested quantifiers — a group ending in + or * that is itself repeated, like (a+)+ or (\d*)* — and for alternations where the branches can match the same text. Making the inner part more specific usually collapses the runtime from minutes to microseconds.
My pattern works here but not in my code. Why?
Two usual suspects. The first is lastIndex, described above: your code probably reuses one g-flagged regex object where this tool builds a fresh one each time. The second is escaping. In a JavaScript string literal, "\d" is just "d" — you need "\\d", or better, a regex literal like /\d+/ where no string escaping happens at all. The pattern box here takes the raw pattern, with no string layer in between.
How do I match a literal dot, or any special character?
Put a backslash in front of it: \. matches a full stop rather than "any character". The characters that need this treatment are . * + ? ^ $ { } ( ) | [ ] \ — and inside a character class the rules relax, so [.] also works and is often easier to read. If you are escaping a string that came from user input, do it programmatically rather than by hand.
Why does $ not match at the end of every line?
Because without the m flag, ^ and $ anchor to the whole string rather than to each line. Add m to your flags and they will match at every line boundary. This trips people up constantly when testing a pattern against pasted multi-line input, because the pattern is right and the flags are not.