Greedy vs Lazy Quantifiers
Quantifiers control how many times something repeats. The catch: by default they are greedy and grab as much as possible. Here is when that bites and how lazy and possessive versions fix it.
The quantifiers
| Greedy | Lazy | Possessive | Repeats |
|---|---|---|---|
* | *? | *+ | 0 or more |
+ | +? | ++ | 1 or more |
? | ?? | ?+ | 0 or 1 |
{2,5} | {2,5}? | {2,5}+ | 2 to 5 |
The greedy trap
Given the string <b>one</b> <b>two</b> and the pattern:
<b>.*</b>
A greedy .* matches the entire string in one go — from the first <b> to the last </b> — because it grabs everything, then backtracks just enough to let </b> match.
The lazy fix
<b>.*?</b>
The lazy .*? matches as little as possible, so you get two separate matches: <b>one</b> and <b>two</b>.
Often better than lazy: a negated class
<b>[^<]*</b>
Instead of "match anything, lazily", say "match anything that is not a <". This avoids backtracking entirely and is usually faster and more predictable.
Possessive quantifiers and catastrophic backtracking
A pattern like (a+)+$ against a long string of as followed by a ! can explode into millions of backtracking steps — a real denial-of-service risk (ReDoS). Possessive quantifiers (a++) and atomic groups (?>...) refuse to give back characters, killing that blow-up. They are supported in PCRE, Java and .NET, but not in stock JavaScript.
Rule of thumb
- Reach for a negated class first (
[^"]*,[^<]*). - Use lazy when there is no clean delimiter to negate.
- Use possessive/atomic when a pattern risks catastrophic backtracking.
FAQ
What does the ? after * or + mean?
It makes the quantifier lazy (non-greedy): the engine matches as few characters as possible while still allowing the overall pattern to succeed. So .*? stops at the first opportunity instead of the last.
Is lazy matching slower than greedy?
Not inherently — it just backtracks in the opposite direction. For structured text a negated character class like [^<]* is usually faster than either, because it never has to backtrack.
What is catastrophic backtracking?
When nested quantifiers create exponentially many ways to match, a small input can hang the engine. Avoid patterns like (a+)+ and use atomic groups or possessive quantifiers to prevent it.