Greedy vs lazy quantifiers in regex
.* and .*? look almost identical, but they match very different things.
The short answer
Greedy (the default): match as much as possible, then back off if needed.
Lazy (add ? after the quantifier): match as little as possible, then extend if needed.
Symbols:
*,+,?,{n,m}— greedy*?,+?,??,{n,m}?— lazy
The classic example
Input: "foo" and "bar"
Greedy: ".*" → matches `"foo" and "bar"` (THE WHOLE THING)
Lazy: ".*?" → matches `"foo"` (stops at first ")
The greedy version starts at the first quote and tries to grab everything. The regex engine then notices there's no closing quote at the end, so it backs off one character at a time until it finds one — which happens at the last ". So it matches the whole sandwich.
The lazy version starts with zero characters and grows by one until the closing quote is found — which happens at the first closing quote. So it matches just "foo".
When to use each
Greedy: when you want a complete match
Greedy is the default for a reason. When matching identifiers, numbers, or anything contiguous, greedy is what you want:
\d+ grabs all digits in a row
\w+ grabs the entire word
[a-z]+ grabs the run of lowercase letters
Lazy: when you want to stop at the first delimiter
Use lazy when there's a closing marker and you want the smallest content between them:
<.*?> each HTML tag, one by one
".*?" each quoted string, one by one
\(.*?\) each parenthesized phrase
The better alternative: negated character class
Often, what people reach for "lazy" to fix is better done with a negated class. Compare:
Lazy: ".*?"
Negated class: "[^"]*"
Both match a string between quotes. The negated class is usually preferable because:
- Clearer intent — "match anything except a quote" reads more naturally than "match anything but stop early."
- No backtracking — the negated class consumes characters that definitely belong, never has to back off.
- Slightly faster — though for most patterns this won't matter.
When lazy isn't enough
Lazy quantifiers handle most cases but can still backtrack. For truly catastrophic backtracking (where the engine explores exponentially many paths on input that almost matches), you need stronger tools:
- Possessive quantifiers (
*+,++,?+) — never give back what they matched, eliminate backtracking entirely. PCRE, Java, Python 3.11+. - Atomic groups (
(?>...)) — same idea applied to a group. PCRE, Java.
The takeaway
Greedy is the default and usually right. Reach for lazy when you have a closing delimiter and want the smallest match — but consider a negated character class first; it's often clearer and faster.
If you find yourself in performance trouble or seeing ReDoS warnings, look at possessive quantifiers or atomic groups (if your flavor supports them).
Related reading
Try this pattern in the explainer
Paste any regex into the live explainer and see what each token means, with example matches in real time.
Open the regex explainer →