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.

By NoFrillzApps · 4 min read

Start from examples, not from the pattern

Before writing anything, collect three kinds of test text:

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

FlagEffect
gFind all matches instead of stopping at the first.
iCase-insensitive.
m^ and $ match at every line start/end, not just the whole input.
s. also matches newline characters.
uUnicode 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:

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.

Related guides