Regex Tester

Write a regex pattern, paste your test string, and see matches highlighted in real time. Capture groups shown below.

Flags

Matched Output

Enter a pattern and test string to see matches...

🟢📖 Regex Quick Reference

.Any character except newline
\dDigit [0-9]\DNon-digit
\wWord char [a-zA-Z0-9_]\WNon-word
\sWhitespace\SNon-whitespace
+One or more*Zero or more
?Zero or one{n,m}n to m times
^Start of line$End of line
( )Capture group(?: )Non-capturing group
|Alternation (OR)[abc]Character class
(?= )Lookahead(?<= )Lookbehind

✂️ Greedy vs lazy: why <.*> matches too much

Quantifiers are greedy by default. They consume as much as possible and only give characters back when the rest of the pattern cannot otherwise be satisfied. Run <.*> against <a><b><c> and you get one match — the entire string — not three. The engine took everything available, then satisfied the closing bracket with the last one it could find.

Appending ? makes the quantifier lazy, so it consumes as little as possible: <.*?> returns <a>, <b>, and <c> as separate matches. This is the most common single-character correction in real regexes. It is also where the usual advice runs out — a lazy dot still crosses newlines under the s flag, and it will happily match across boundaries that a real parser would treat as structure.

Greedy is not a bug, it is answering a different question. To take the last path segment of /a/b/c, the greedy /[^/]*$ is correct. Character classes like [^/] are the safer instrument whenever you know what the field cannot contain, because they physically cannot overrun the delimiter the way .* can.

💥 Catastrophic backtracking: the regex that hangs a process

A backtracking engine explores alternatives. When a pattern admits several ways to divide the same input, that exploration can grow exponentially with input length. Take ^(a+)+$ against a run of a characters followed by a single !. The engine tries every partition of the input among the nested quantifiers before it can conclude that the anchor will never be satisfied.

The numbers do not degrade gracefully. At 20 as the work is on the order of 2^20 — milliseconds. At 30 it is 2^30, about a billion steps, and the thread stops responding. Thirty-one characters is enough to occupy a CPU core. This is the mechanism behind ReDoS, and it applies to every engine that backtracks: JavaScript, Python, Java, and PCRE alike.

Patterns that trigger it share one shape — nested quantifiers over overlapping alternatives, such as (a+)+, (a|a)*, or (\w+\s?)*. Untrusted input reaching one of those is a denial-of-service waiting for a request.

Three ways out. Rewrite so the alternatives cannot overlap: ^a+$ accepts exactly the strings ^(a+)+$ accepts and runs in linear time. Bound the input length before matching, which caps the worst case even when the expression cannot be rewritten. Or move the check to an engine with a different design — RE2, Go's regexp, and Rust's regex crate guarantee linear time by giving up backreferences and lookaround. That trade is usually acceptable for validation, which is where the untrusted input actually arrives.

🎯 Flags, and the stateful /g bug

  • i — case-insensitive.
  • u — Unicode mode. Fixes surrogate-pair handling so that . and quantifiers operate on code points rather than UTF-16 units. Without it, a single emoji counts as two characters.
  • m — multiline. Makes ^ and $ match at line breaks instead of only at the start and end of the whole string. Without it, ^foo against multi-line text matches at most once, which is the usual explanation for "it only found the first one".
  • s — dotall. Without it, . excludes \n, which is why a pattern that works on one line fails on two.
  • g — global. This is the one that causes bugs.

A regex carrying g keeps a lastIndex cursor between calls. That makes re.test() return alternating results when the same compiled object is reused — the symptom is a validation function that accepts a value, rejects the identical value on the next call, then accepts it again. Either omit g when you are only testing, or reset re.lastIndex = 0 before each use.

One more thing the tester cannot tell you: dialects are not interchangeable. Lookbehind arrived in JavaScript with ES2018 but does not exist in RE2 or Go. Backreferences are unavailable in RE2. Possessive quantifiers and atomic groups work in PCRE and Java but not in JavaScript. A pattern verified here can still fail in a Go service, for reasons this page has no way to display.