Regex Tester

Highlight matches in real time, show capture groups, with g/i/m toggles

Regex tester: flags, greedy matching, and backtracking traps

What the three flags actually change

FlagEffectCommon misunderstanding
g globalFind all matches, not just the firstIt is stateful, see the next section
i ignore caseLetter casing is ignoredIt also expands character-class casing (so [a-z] will match uppercase too)
m multiline^ and $ match the start and end of every line, not just the whole stringThis is the switch that lets you "match line by line"; without it ^ only matches the very start of the text

The third row is the most often forgotten. ^ERROR without m can only match the beginning of the first line of the whole text; when ERROR appears on line 100 it will not hit. Turn on m and each line is treated as an independent line.

There is also a frequently missed flag, s (dotAll), which makes . match newline characters too. By default . does not match newlines, so when you try to match across lines, .* silently fails — this is the number-one reason people ask "why does my cross-line match not work".

The g flag is stateful

This is a real trap that makes "the same regex work sometimes and fail sometimes". A regex object created with g keeps an internal position pointer (lastIndex):

The consequence: reusing the same g regex object against several strings inside a loop drops matches. The correct fix is to create a fresh regex object for each match, or manually zero lastIndex before reusing. The decision rule is "does this pattern appear once or many times in a single string" — when you only test for existence, you should not add g at all.

Greedy versus lazy: why a match is longer than expected

Quantifiers are greedy by default: they match as much as possible. Adding ? makes them lazy: match as little as possible.

InputPatternResult
<a>1</a><a>2</a><a>.*</a>The entire string (greedy, jumps past the first </a> to the last)
same as above<a>.*?</a><a>1</a> (lazy, stops at the first)

To extract "the content of every tag" you must use lazy matching, otherwise you only get one big span from the first tag to the last. But lazy matching also carries a backtracking cost, covered in the next section.

Catastrophic backtracking: regex can freeze the browser

Nested quantifiers produce exponential backtracking. The classic example is (a+)+$ run against a long run of a followed by a b (such as aaaaaaaaaaaaaaaaaaaaaaaaX).

The engine tries every possible way to group the a characters, and the count grows exponentially with the length. Twenty a characters are tolerable; thirty can make the page unresponsive.

In real projects these patterns usually show up as (.*)*, ([a-z]+)*, or several .* wrapped around optional parts. Defenses:

Quick reference of common constructs

SyntaxMeaning
\d \w \sDigit / word character / whitespace (uppercase forms negate)
[abc] [^abc]Character set / negated set
^ $Line start / line end (per-line when m is on)
\bWord boundary, used to stop id hitting userId
(?:...)Non-capturing group, groups without consuming a number
(?<name>...)Named capture group
(?=...) (?!...)Lookahead: what must / must not follow
(?<=...) (?<!...)Lookbehind: what must / must not precede

Two easy-to-forget rules inside character classes: a . inside [] is a literal dot (no escaping needed), and a - at the start or end is also a literal hyphen, becoming a range only when placed in the middle.

Two problem classes regex is the wrong tool for

This tool supports live match highlighting, shows each match and its capture groups, and offers g/i/m toggles so you can verify a pattern's behavior one case at a time. All testing runs locally in your browser.

FAQ

Why is only one match highlighted sometimes?

Without g (global) checked, the regex matches only the first result. With g it highlights all matches.

What is shown for empty capture groups?

Groups that did not participate in the match show as "undefined", which is normal and usually happens when an optional group is unmatched.