ToolKitSphere IconToolKitSphere
Developer Utilities

Greedy vs Lazy Matching in Regex

Online Tools Platform Team6 min read

You write <.+> to pull the first HTML tag out of a string, run it against <b>bold</b>, and get back the entire string instead of <b>. Nothing is broken. That is greedy matching working exactly as designed, and understanding why takes about five minutes and saves you from guessing at quantifiers forever. This post walks through what the engine actually does, when to reach for a lazy quantifier, and why the best answer is often neither. For the wider syntax picture, see The Complete Guide to Regular Expressions.

Greedy Is the Default

Every quantifier — *, +, ?, {2,5} — is greedy unless you say otherwise. Greedy means: consume as many repetitions as the input allows, then let the rest of the pattern try to match, and give characters back one at a time only when it fails.

Trace <.+> against <b>bold</b> character by character:

  1. < matches the < at position 0.
  2. .+ is greedy, so it consumes everything to the end of the string: b>bold</b>.
  3. The pattern still needs a >, but the cursor is at the end. No characters left.
  4. The engine backtracks: .+ gives back one character, so it now holds b>bold</b, and the cursor sits on the final >.
  5. > matches. Done.

The result is <b>bold</b> — the whole thing. The engine did not misunderstand you. It found the leftmost match, and among matches starting there, the greedy quantifier steered it to the longest one.

Lazy Reverses the Preference

Append a ? to any quantifier and it becomes lazy (also called non-greedy or reluctant): *?, +?, ??, {2,5}?. A lazy quantifier takes the minimum, tries the rest of the pattern, and expands only when forced.

Same input, pattern <.+?>:

  1. < matches at position 0.
  2. .+? takes the minimum it is allowed — one character, b.
  3. The pattern needs >, and the cursor is on >. It matches.
  4. Done. The match is <b>.

With the global flag, a second match picks up at </b>. That is usually what people wanted the first time.

The same swap fixes quoted-string extraction. Against say "hi" and "bye", the pattern ".*" returns "hi" and "bye" — greedy runs to the end and backtracks to the last quote. ".*?" returns "hi", then "bye" on the next iteration.

Try both in the Regex Tester & Matcher side by side. Watching the highlight snap from the whole string to a single tag when you add one ? makes the idea stick faster than any explanation.

Lazy Does Not Mean "Shortest Match"

This is where people get burned. Laziness only decides how much a quantifier consumes once the starting position is already fixed. The engine picks the starting position first, scanning left to right, and it never abandons a start position that produces a match in favour of a shorter match further right.

Run \w+?\d against abc1:

  • Start at index 0 (a). \w+? takes a. Next the pattern needs \d, but the cursor is on b. Fail.
  • Expand to ab. Cursor on c, not a digit. Fail.
  • Expand to abc. Cursor on 1, which is a digit. Match.

The result is abc1, not c1, even though c1 is shorter and also valid. Leftmost beats shortest, always. If you genuinely want the trailing fragment, anchor or constrain the start — do not expect laziness to do it.

Usually, a Negated Class Beats Both

<.+?> works, but it says something imprecise: "any characters, as few as possible, up to a >." What you actually mean is "characters that are not >." Say that instead:

<[^>]+>

[^>] is a negated character class: any single character except >. The quantifier is greedy, but it physically cannot cross a >, so it stops at the right place with no backtracking at all. Against <b>bold</b> it matches <b> immediately.

The same rewrite applies everywhere the lazy form shows up:

Lazy version Negated-class version
".*?" "[^"]*"
<.+?> <[^>]+>
\(.*?\) \([^)]*\)
/\*.*?\*/ no simple equivalent — the delimiter is two characters

Three reasons to prefer the right-hand column. It is faster on long input, because the engine never explores positions it will have to undo. It fails faster too, which matters when the pattern is applied to untrusted input — chains of .*? are a common ingredient in catastrophic backtracking. And it documents intent: the delimiter appears explicitly in the pattern, so the next reader does not have to reason about quantifier preference.

One caveat: a negated class matches newlines, while . does not unless the dotAll flag is set. "[^"]*" will happily span a line break where ".*?" would not. If single-line behaviour matters, exclude them: "[^"\r\n]*".

Greedy and Lazy in Replacements

The distinction bites hardest during find-and-replace, because a greedy pattern silently eats text between the things you meant to change. Stripping tags from <p>one</p><p>two</p> with <.+> replaces the entire string with nothing — including one</p><p>two. With <[^>]+> you get onetwo, which is what you were after.

Dry-run substitutions before you apply them. Regex Find & Replace shows the output next to the input so a greedy overreach is visible immediately, and like everything on this site it runs locally in your browser.

Capture groups behave the same way. ^(.*)-(.*)$ against a-b-c gives group 1 a-b and group 2 c, because the first greedy .* claims as much as it can. Make the first one lazy — ^(.*?)-(.*)$ — and you get a and b-c. Neither is wrong; you just have to pick which split you meant.

Locking In a Greedy Match

Sometimes you want greedy behaviour and you want the engine to stop reconsidering it. That is what possessive quantifiers (++, *+, ?+) and atomic groups ((?>...)) do: they match greedily and then throw away all the backtracking positions. If the rest of the pattern fails, the whole attempt fails immediately instead of grinding through every shorter split.

\d++abc against a long run of digits fails almost instantly, while \d+abc retries every possible boundary. Java, PCRE, PHP, .NET, and Ruby support both forms. JavaScript supports neither today, so in JS you emulate an atomic group with a lookahead plus a backreference — (?=(\d+))\1 — or, more sensibly, restructure the pattern so there is nothing ambiguous to backtrack through. Lookarounds have plenty of other uses too; Regex Lookahead and Lookbehind Made Simple covers them in full.

The Short Version

Greedy takes everything and gives it back reluctantly. Lazy takes nothing and adds reluctantly. Neither one changes where a match starts, only how far it extends from there. When a delimiter defines the boundary, skip the debate entirely and use a negated character class — it is clearer, faster, and immune to the failure modes both quantifier styles share.

When a pattern still surprises you, decompose it in the Regex Explainer & Pattern Breakdown and check which quantifiers are greedy. Nine times out of ten, the one that is eating your string is right there in the first ten characters.

Frequently asked questions

What is the difference between greedy and lazy quantifiers?

A greedy quantifier such as * or + consumes as much text as it can, then hands characters back one at a time until the rest of the pattern can match. A lazy quantifier, written by appending ? as in *? or +?, consumes as little as possible and takes one more character only when the rest of the pattern fails.

How do I make a regex non-greedy?

Add a question mark directly after the quantifier. Greedy *, +, ?, and {2,5} become lazy *?, +?, ??, and {2,5}?. Laziness is set per quantifier — there is no flag in mainstream engines that flips every quantifier in a pattern at once, so each one you want to change needs its own ?.

Why does my regex match too much text?

Almost always because a greedy .* or .+ ran to the end of the string and then backtracked to the last possible position for the next part of the pattern. Running <.+> against <b>bold</b> returns the whole string rather than <b> for exactly this reason. Use a lazy quantifier or a negated character class.

Is a lazy quantifier faster than a greedy one?

Not inherently. Lazy is faster when the match ends near the start position and greedy is faster when it ends near the end, because each one avoids backtracking in its favourable case. Both do the same total amount of work in the worst case, and a negated character class usually beats both.

Does the lazy quantifier find the shortest possible match?

No. It finds the shortest match starting at the leftmost position where any match exists. Leftmost always wins over shortest, so \w+?\d against abc1 returns abc1, not c1, because the engine commits to starting at a before it considers how little it can consume.

When should I use a negated character class instead of a lazy quantifier?

Whenever the text you want to skip is bounded by a known delimiter. Replace <.+?> with <[^>]+> and ".*?" with "[^"]*". The negated class cannot cross the delimiter at all, so there is no backtracking to undo and the intent is stated directly in the pattern.

What are possessive quantifiers and atomic groups?

They are greedy quantifiers that refuse to give characters back. Possessive forms are written ++, *+, and ?+; an atomic group is (?>...). Both discard backtracking positions, which makes failing matches fail quickly. Java, PCRE, PHP, .NET, and Ruby support them; JavaScript does not.

Try the related tools

Related articles