Non-Greedy Quantifiers — *?, +?, and ?? Explained
Master non-greedy (lazy) quantifiers *?, +?, ?? in regex. Learn when to use them, how they differ from greedy quantifiers, and the negated-class alternative.
Detailed Explanation
Non-Greedy (Lazy) Quantifiers
By default, quantifiers in regex are greedy: they consume as much as possible and only give back when forced. Non-greedy quantifiers do the opposite: they consume as little as possible while still allowing the overall match to succeed.
The Three Lazy Quantifiers
| Greedy | Lazy | Meaning |
|---|---|---|
* |
*? |
Zero or more, prefer fewer |
+ |
+? |
One or more, prefer fewer |
? |
?? |
Zero or one, prefer zero |
Greedy vs Lazy in Action
Input: <b>bold</b><i>italic</i>
Greedy: <.+> matches "<b>bold</b><i>italic</i>" (whole string)
Lazy: <.+?> matches "<b>" (first tag only)
The greedy version overshoots because .+ consumes everything until the last >.
Tested Examples
| Pattern | Input | Match |
|---|---|---|
a.*b (greedy) |
"axbyb" |
axbyb |
a.*?b (lazy) |
"axbyb" |
axb |
<.+> |
"<a><b>" |
<a><b> |
<.+?> |
"<a><b>" |
<a> |
".+?" |
'"a","b"' |
"a" |
The Negated-Class Alternative
Lazy quantifiers can backtrack badly on long inputs. Often a negated character class is faster and clearer:
Lazy: "(.+?)"
Better: "([^"]+)"
The negated version reads "match characters that are not a quote" and is much faster because it never backtracks.
When Lazy Wins
When the closing delimiter can also appear inside, you cannot use a simple negated class. For example, matching nested tags or escaped quotes requires either a lazy quantifier or an unrolled-loop pattern ([^"\\]|\\.).
Possessive Quantifiers (Not in JavaScript)
Some engines (PCRE, Java) support possessive quantifiers (*+, ++) that never give back. JavaScript does not, but you can simulate them with atomic-group emulation: (?=(pattern))\1.
Practical Recommendations
- Default to greedy + negated class when possible (
[^>]+over.+?) - Use lazy when delimiters can nest or repeat
- Test pathological inputs to catch performance issues early
Use Case
Extracting the first occurrence of a delimited substring (HTML tag, quoted text, JSON value), parsing log lines where you want the smallest match between fixed boundaries, or matching balanced delimiters where greedy patterns overshoot.