A regular expression that “almost works” is one of the most common time sinks in programming. The fix is usually small — a flag, a lazy quantifier, an escaped character — but spotting it is much easier when you can see matches highlighted live.
This guide covers the flags, groups and quantifiers that matter, and how to debug a pattern that isn’t behaving.
TL;DR — Paste your pattern and test text into the regex tester, toggle flags, and watch matches highlight as you type. Use lazy quantifiers (
*?,+?) when a match grabs too much.
Flags change everything
Flags are the modifiers after the pattern. The ones you’ll use most:
- g — global: find all matches, not just the first.
- i — case-insensitive.
- m — multiline:
^and$match at line breaks. - s — dotall:
.also matches newlines. - u — unicode: correct handling of code points and
\p{...}classes. - y — sticky: match only at the current position.
Many “it doesn’t find all of them” bugs are just a missing g, and many “it stops at the first line” bugs are a missing m.
Greedy vs. lazy matching
By default, * and + are greedy — they match as much as they can and then back off. So <.*> against <a><b> matches the whole <a><b>, not just <a>. Add ? to make them lazy: <.*?> matches <a> and <b> separately. This single character fixes a huge share of “it matched too much” problems.
Capture groups
Parentheses create capture groups, which let you extract parts of a match:
(\w+)@(\w+\.\w+)
Against dev@example.com, group 1 is dev and group 2 is example.com. Use named groups for readability — (?<user>\w+)@(?<domain>\S+) — and reference them by name instead of number. The tester lists every group per match so you can confirm you’re capturing the right spans.
Escape the special characters
Characters like ., *, +, ?, (, ), [, ], {, }, ^, $, | and \ have special meaning. To match them literally, escape with a backslash: \. matches a real dot, not “any character”. Forgetting this is why 3.14 unexpectedly matches 3x14.
Debug it live
The fastest way to fix a stubborn pattern is to build it up incrementally and watch the highlights. Start with the simplest version that matches something, then tighten it one token at a time in the regex tester — it uses the same JavaScript engine as your browser and Node, so what you see is exactly what your code will do.