How to test a regular expression without getting burned
A regex that matches your one example is easy. One that matches every valid input, rejects everything else and doesn't freeze on hostile text takes a method.
Start from examples, not from the pattern
Before writing anything, collect three kinds of test text:
- Should match: typical cases and the edge cases (shortest, longest, unusual but valid).
- Should not match: near misses that look right but aren't.
- Hostile input: very long strings and repeated characters, to check performance.
Paste them all into the test text of the Regex Tester at once, then grow the pattern until the highlighting is exactly right.
A worked example: ISO dates
Goal: find dates like 2026-10-14 in a log.
Deployed 2026-10-14, rolled back 2026-10-15.
Ticket 12026-10-140 is not a date.
Due 2026-1-5 (not zero-padded).
A first attempt, \d{4}-\d{2}-\d{2}, matches both real dates but also 2026-10-14 inside 12026-10-140. Add word boundaries so a match can't start or end in the middle of a number:
\b\d{4}-\d{2}-\d{2}\b
Now only the two real dates match, and the non-padded 2026-1-5 is correctly ignored. To pull out the parts, add capture groups, with names for readability:
\b(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})\b
Note what the regex does not do: 2026-13-45 still matches. Regexes check shape, not meaning. Validate ranges in code after matching.
Flags change the meaning
| Flag | Effect |
|---|---|
g | Find all matches instead of stopping at the first. |
i | Case-insensitive. |
m | ^ and $ match at every line start/end, not just the whole input. |
s | . also matches newline characters. |
u | Unicode mode: correct handling of characters outside the Basic Multilingual Plane (e.g. emoji) and \p{...} classes. |
A pattern that "works" in one tool and fails in your code very often differs only in flags: check that your code uses the same ones you tested with.
Greedy vs lazy
Quantifiers (*, +, {n,m}) are greedy: they take as much as they can. On <b>one</b> and <b>two</b>, the pattern <b>.*</b> matches the whole line, from the first <b> to the last </b>. Adding ? makes it lazy: <b>.*?</b> produces two separate matches. Often the cleaner fix is to say what can't appear: <b>[^<]*</b>.
Catastrophic backtracking
Backtracking engines (JavaScript, Python, Java, PCRE) try alternatives one by one. Nested quantifiers on overlapping input make the number of alternatives explode:
^(a+)+$ tested against aaaaaaaaaaaaaaaaaaaaaaaaaaaa!
Each extra a roughly doubles the work before the engine concludes there's no match. With user-supplied input this is a denial-of-service bug (ReDoS). Warning signs: a quantified group that contains another quantifier ((a+)+, (\w*)*), or alternatives that can match the same text ((a|a)*). Rewrite so each character can be matched in only one way: here, simply ^a+$.
This is why the Regex Tester stops after two seconds and caps input at 10,000 characters: a runaway pattern can't hang your browser tab.
JavaScript is not PCRE
Most regex syntax is shared, but flavours differ in details that matter when you copy a pattern between languages:
- Named groups are
(?<name>…)in JavaScript; Python'sreuses(?P<name>…). - JavaScript has no possessive quantifiers or atomic groups; PCRE and Java do.
\dmeans only ASCII 0-9 in JavaScript, but in Python 3 (str patterns) it matches any Unicode decimal digit.
Test in the same flavour you'll run. The Regex Tester uses your browser's JavaScript engine, which is exactly what front-end and Node.js code will use.
Common questions
Why does my regex work in one tool but not in my code?
Usually a different flag (g, i, m, s, u) or a different regex flavour. JavaScript, Python and PCRE differ in details such as named-group syntax, lookbehind support and what \d matches.
What is catastrophic backtracking?
Nested quantifiers such as (a+)+ let a backtracking engine try an exponential number of ways to match, so a short hostile input can take seconds or minutes. Rewrite the pattern so each character can be matched in only one way.
Can a regex validate an email address or a date?
A regex can check the shape (something@something.tld, or NNNN-NN-NN), but not meaning: it will accept 2026-13-45. Check ranges and real validity in code after matching.
What is the difference between greedy and lazy quantifiers?
Greedy quantifiers (*, +) take as much text as possible; adding ? (*?, +?) makes them take as little as possible. Lazy matching is the usual fix when a match runs past where you expected it to stop.
Try it: Regex Tester — runs in your browser, nothing uploaded.