
Regex greedy vs lazy matching: why your pattern grabs too much
Published Jul 24, 2026
You write <b>.*</b> to grab the contents of a bold tag, test it on <b>Hello</b>, it works perfectly. Then you run it on real HTML with two bold tags on the same line — <b>Hello</b> and <b>World</b> — and instead of two matches, you get one giant match spanning both tags. This isn’t a bug in your regex engine; it’s the default behavior of *, +, and {n,m}, and it has a one-character fix once you know what’s happening.
Why .* grabs more than you expect
By default, quantifiers in regex are greedy — .* doesn’t mean “match some characters,” it means “match as many characters as possible, then back off only if the rest of the pattern absolutely requires it.”
Walk through <b>.*</b> against <b>Hello</b> and <b>World</b> step by step:
<b>matches the first<b>..*starts by consuming the entire rest of the string — everything up to the end, including the second<b>World</b>.- The engine then needs to find
</b>to finish the match, so it starts backing off from the end of.*one character at a time. - The first place (scanning backward from the end) where
</b>fits is the very last</b>in the string — not the first one it “logically” should stop at.
So the match ends up being <b>Hello</b> and <b>World</b> in its entirety, because greedy matching always tries the longest possible string first and only shrinks it as little as necessary to make the rest of the pattern succeed.
The fix: make the quantifier lazy with ?
Adding a ? immediately after a quantifier flips it from greedy to lazy (also called “non-greedy” or “reluctant”): *?, +?, ??, {n,m}?.
A lazy quantifier does the opposite: it starts by matching as few characters as possible, then only expands if the rest of the pattern can’t succeed yet.
<b>.*?</b> against the same string:
<b>matches the first<b>..*?starts by matching zero characters.- The engine checks: does
</b>match right here? No (we’re at “Hello…”, not yet at a</b>) — so it expands.*?by exactly one character and checks again. - This repeats character-by-character until
.*?has consumed exactlyHello, at which point</b>matches immediately.
Result: <b>Hello</b> and <b>World</b> come back as two separate matches, which is almost always what you actually wanted when parsing tag-like or delimiter-like structures.
When you actually want greedy (it’s not just “the wrong default”)
Greedy isn’t a mistake in the language — it’s correct for a different, equally common case: matching the outermost boundary of something, not the smallest unit inside it. If you’re extracting “everything between the first { and the last }” in a blob of JSON-like text (say, to grab a whole object regardless of nesting), greedy is exactly right and lazy would stop at the first inner } instead, giving you a truncated, invalid fragment.
The rule of thumb: lazy for repeated small delimited chunks (tags, quoted strings, list items); greedy for “grab the whole outer span.”
Quick reference
| Pattern | Behavior | Use it when |
|---|---|---|
.*, .+, {n,m} |
Greedy — matches longest possible | You want the outermost span, or there’s only one match in the string |
.*?, .+?, {n,m}? |
Lazy — matches shortest possible | You have multiple similar delimited chunks and want each one separately |
If you’re not sure which one a pattern is actually doing on your real input rather than your test string, running it against the actual multi-match text in a regex tester with live match highlighting makes the difference immediately visible — you’ll see one giant highlighted block for greedy versus several separate ones for lazy, which is usually faster than reasoning through it character-by-character.