本页面有中文版。
Cette page est disponible en français.
Esta página está disponible en español.
Guides1 live hunt inside

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.

Regex Hunter logo

· 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.

Matches1/1 as expected
/<.+>/
  • <b>bold</b> and <i>it</i>
Try it in the Playground
One match, from the first < to the last >. The .+ ran to the end of the line, then backed up just far enough to find a >.

Here's what the engine does with <.+>:

  1. < matches the first character.
  2. .+ swallows everything to the end of the line.
  3. > now has nothing left to match, so the engine backtracks: .+ gives back one character at a time until > fits.
  4. 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.

Matches1/1 as expected
/<.+?>/
  • <b>bold</b> and <i>it</i>
Try it in the Playground
Four separate matches, one per tag. The lazy .+? stops at the first > it can.

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:

Matches1/1 as expected
/".*?"/
  • say "hi" and "bye"
Try it in the Playground
Two matches, "hi" and "bye". The greedy ".*" would match "hi" and "bye" as one string, quotes in the middle included.

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 >".

Matches1/1 as expected
/<[^>]+>/
  • <b>bold</b> and <i>it</i>
Try it in the Playground
Same four tags as the lazy version, but the rule is in the class itself. [^>] can never run past a >.

<[^>]+> 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:

Matches1/1 as expected
You might expect "ab", the shortest piece. But the match starts at the first a, and from there the lazy .*? has to stretch to reach the b.

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:

Live huntpar 13

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.

Try it in the Playground
Like this? A new one drops every day.Play today’s hunt

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