Greedy vs lazy quantifiers: why .* eats too much
Regex quantifiers grab as much text as they can. Here's how greedy and lazy matching really work, why <.+> swallows a whole line of HTML, and when a negated class beats both.

· 6 min read
/<.+?>/
match
- <b>
- </i>
- <br>
spare
- b
- i>
- <>
You write <.+> to grab an HTML tag, run it on <b>bold</b>, and it matches the entire string, both tags and the text between them. Nothing is broken. The quantifier did exactly what it's designed to do: take as much as it possibly can.
This post explains what greedy and lazy quantifiers actually do, why the difference only shows up in what gets matched, and why the best fix is often neither of them.
01Greedy: take everything, then give back
The quantifiers *, +, ? and {n,m} are greedy by default. They match as many characters as they can, and only give characters back if the rest of the pattern fails.
Here's what the engine does with <.+>:
<matches the first character..+swallows everything to the end of the line.>now has nothing left to match, so the engine backtracks:.+gives back one character at a time until>fits.- The first
>it finds while backing up is the last one in the line. Match found.
02Lazy: take as little as possible
Add a ? after any quantifier and it becomes lazy: it matches as few characters as possible and only takes more when the rest of the pattern fails.
The lazy versions of every quantifier:
| Greedy | Lazy | Meaning |
|---|---|---|
* |
*? |
zero or more |
+ |
+? |
one or more |
? |
?? |
optional |
{2,5} |
{2,5}? |
between 2 and 5 |
Same with quoted strings. Greedy runs from the first quote to the last one. Lazy stops at the next quote:
03Often better than both: a negated class
Lazy quantifiers still try to match . against every character, including the one you're trying to stop at. A negated character class says the stopping rule directly: "anything except >".
<[^>]+> and "[^"]*" are usually the better choice:
- Clearer. The pattern says what a tag is, not how the engine should search for one.
- Faster. There's no backtracking:
[^>]+stops at the first>by definition. - Works across lines.
.doesn't match a line break by default, so<.+?>misses a tag split over two lines.[^>]matches line breaks too.
04The surprise: lazy isn't "shortest match"
Lazy means as short as possible from where the match starts. It doesn't move the start. The engine always reports the leftmost match it can find:
If you want the shortest a…b with no a inside, say so with a class again: a[^a]*b.
05Why it doesn't matter in regex golf
Here's a useful fact for golfers: greedy versus lazy never changes whether a string matches. a.*b and a.*?b match exactly the same set of words. They only differ in which part of the text they report. Since a hunt only asks "does this word match?", a ? after a quantifier costs you a character and buys nothing.
What does help on boards is the negated class, because it changes what can match. On this board, every monster has exactly one a:
Catch the words with exactly one a.
Targets · 0/5 caught
- cattarget
- breadtarget
- snaketarget
- planttarget
- magictarget
Innocents · 0 hit
- bananainnocent
- datainnocent
- saladinnocent
- pluminnocent
- treeinnocent
Type a regex — the board updates as you go.
06Quick reference
- Greedy (
.*,.+): match as much as possible, then back off. The default. - Lazy (
.*?,.+?): match as little as possible, then extend. Add?. - Negated class (
[^>]*): match anything except the stop character. Usually the best tool when you know where to stop. - Lazy and greedy match the same strings. They differ in what they capture, extract or replace.
07Keep practising
- The quantifiers section of the guide has more live examples.
- Today's hunt is waiting.