</>DevTools

.*Regex Tester

Test regular expressions with live matching

//
Common Patterns:

Matches: 0

No matches found

Using a Regex Tester, and the Traps That Catch People

Regular expressions are powerful because they're terse — and hard to debug for the same reason. A tester's value isn't teaching you syntax; it's showing you immediately how your pattern actually behaves against real data. Below is a syntax reference plus the pitfalls that produce patterns which work but are wrong.

Core syntax

PatternMeaningExample
.Any character except newlinea.c → abc, a1c
\d \w \sDigit / word character / whitespace\d{3} → 123
\D \W \SNegations of the above\D → any non-digit
[abc] [^abc]Character class / negated class[aeiou] → one vowel
* + ?Zero or more / one or more / optionalab+ → ab, abb
{n} {n,} {n,m}Exactly n / at least n / n to m\d{2,4} → 12, 1234
^ $Start / end of string (or line)^abc$ → only abc
\bWord boundary\bcat\b → cat, not category
(...)Capturing group(\d+)-(\d+)
(?:...)Non-capturing group(?:ab)+
(?<name>...)Named group(?<year>\d{4})
a|bAlternationcat|dog
(?=...) (?!...)Lookahead / negative lookahead\d(?= USD) → the 0 in '100 USD'
(?<=...) (?<!...)Lookbehind / negative lookbehind(?<=\$)\d+ → 100 in '$100'

What the flags do

  • g (global): find every match instead of stopping at the first. Without it, replace changes only one occurrence.
  • i (ignoreCase): case-insensitive matching.
  • m (multiline): ^ and $ match at each line's start and end rather than only the whole string's.
  • s (dotAll): lets . match newlines too — needed for blocks spanning multiple lines.
  • u (unicode): operates on code points and enables \p{...} Unicode property escapes.

How to use it

  1. Enter the pattern. Type the body only, without wrapping slashes.
  2. Select the flags you need. For multi-line input, consider m and s.
  3. Paste your test string; matches are highlighted in place.
  4. If the pattern has capture groups, check each group's value to confirm you captured exactly what you wanted.
  5. Matching more than intended? See the greedy-quantifier section. Matching only once? Check the g flag.

Greedy versus lazy quantifiers

* and + are greedy by default: they consume as much as possible, then back off one character at a time when the rest of the pattern fails. That's why <.+> written to match an HTML tag swallows all of <b>hello</b> as a single match.

Adding ? makes a quantifier lazy, consuming as little as possible, so <.+?> matches <b> and </b> separately. The more precise approach, though, is a negated character class: <[^>]+> resolves without backtracking, making it both faster and more predictable.

Same input, three results
const s = '<b>hello</b>';

s.match(/<.+>/)[0]      // "<b>hello</b>"  ← greedy
s.match(/<.+?>/)[0]     // "<b>"           ← lazy
s.match(/<[^>]+>/)[0]   // "<b>"           ← preferred

The performance trap: catastrophic backtracking

Some regexes appear to hang forever. It happens with nested quantifiers — shapes like (a+)+ or (\s*,\s*)*. When a match fails, the engine tries every possible way to split the input among the quantifiers, and the number of ways grows exponentially with input length. Even 30 characters can be effectively unbounded.

This is how validating user input with a regex becomes a ReDoS (regular expression denial of service) vulnerability: an attacker sends one carefully non-matching string and pins your CPU. The defenses are to avoid nested quantifiers, use negated character classes to make boundaries explicit, and move genuinely complex validation out of regex and into parsing code.

A dangerous pattern and a safe rewrite
// dangerous: nested quantifiers
/^(\w+\s?)+$/.test("aaaaaaaaaaaaaaaaaaaaaaaaaaaa!")  // extremely slow

// safe: explicit boundaries
/^\w+(?:\s\w+)*$/.test("...")

What not to use regex for

  • Fully validating email addresses: a truly RFC 5322-conformant pattern runs to hundreds of characters and is useless in practice. Screen the shape with something like /^[^@\s]+@[^@\s]+\.[^@\s]+$/ and establish real validity by sending a confirmation email.
  • Parsing HTML: nested structure can't be handled reliably by regex. Use DOMParser or a real parser.
  • Parsing JSON: JSON.parse exists.
  • Validating URLs: parsing with new URL() and checking for an exception plus the scheme is more accurate.
  • Password policy checks: rather than stacking lookaheads, test each rule separately so you can tell the user which one failed.

Working with non-ASCII text

Precomposed Hangul syllables occupy U+AC00–U+D7A3, so [가-힣] matches them; add ranges like [ㄱ-ㅎㅏ-ㅣ가-힣] to include jamo. With the u flag you can instead write \p{Script=Hangul}, which states the intent more clearly.

Watch out for \w: it means ASCII alphanumerics plus underscore only. Non-Latin scripts aren't included, so \w+ won't match them — and for the same reason \b word boundaries don't behave as you'd expect there either.

Frequently Asked Questions

It works in the tester but not in my code.
Most often it's string-literal escaping: new RegExp("\d") means d, not \d, so you need "\\d". A literal /\d/ avoids the problem entirely. The second cause is dialect differences — lookbehind and named groups vary in support across languages and versions.
replace only changes the first match.
You're missing the g flag: use /a/g rather than /a/. If replacing everything is the intent, the string method replaceAll states it more clearly — note that passing a regex without the g flag to replaceAll throws.
lastIndex is giving me strange results.
A regex object with the g or y flag keeps a lastIndex state. Calling test repeatedly on the same object advances the search position, producing alternating true and false. Create a fresh regex each time, or reset lastIndex to 0 before each call.
When should I use a non-capturing group?
Whenever the grouping exists only to apply a quantifier or bound an alternation rather than to extract a value, use (?:...). Needless captures shift group numbers and make code fragile, plus they carry a small performance cost. When you do extract values, named groups (?<name>...) read better than numbers.
^ isn't matching each line of my multi-line text.
Enable the m flag — by default ^ and $ anchor only to the whole string. And if you need to match a block spanning several lines, such as a log entry, you'll want the s flag as well.
How do I make matching case-insensitive?
Use the i flag. Note that writing [a-zA-Z] *and* enabling i is redundant. Also, some languages have locale-specific casing rules (Turkish dotless i, for one), so for locale-sensitive comparisons it's safer to normalize with toLocaleLowerCase first.

💡 Note: Don't try to write a complex pattern in one go. Start small, test, and extend piece by piece — and once it works, leave a comment or test cases beside it. Your future self is a stranger.

🔗Related Tools💻 Regex / Code