Skip to content

Regex Quantifiers: Greedy vs Lazy Matching

Sep 2, 2026 · Formats & Standards

Regex quantifiers control how many times a pattern element repeats, and by default they are greedy, matching as much text as possible before backtracking to let the rest of the pattern succeed. Understanding greedy versus lazy behavior is the difference between a pattern that captures exactly what you meant and one that swallows half your document. This article covers the core quantifiers, what greedy and lazy really do under the hood, how backtracking drives both, and works through concrete examples in a table.

What are the core quantifiers?

The four core quantifiers describe repetition of whatever immediately precedes them. Each applies to a single element: a literal character, a character class, a group, or an escape.

Quantifier Meaning Equivalent
* Zero or more {0,}
+ One or more {1,}
? Zero or one (optional) {0,1}
{n} Exactly n times
{n,} At least n times
{n,m} Between n and m times

So a+ matches one or more a characters, d{3} matches exactly three digits, and colou?r matches both color and colour because the u is optional. The braces form is the most explicit: d{2,4} means between two and four digits inclusive. Note that ? plays two roles, which is a common source of confusion. On its own it is the “zero or one” quantifier. Placed after another quantifier it changes that quantifier from greedy to lazy, which is the topic of the next sections.

What does greedy matching mean?

Greedy matching means the quantifier consumes as much input as it possibly can, and only relinquishes characters if doing so is the only way for the rest of the pattern to match. All of the core quantifiers are greedy by default. They start by grabbing the maximum, then walk backward as little as necessary.

The classic demonstration uses HTML-like text. Suppose you have the string <b>bold</b> and you write the pattern <.*>, intending to match a single tag. The .* is greedy, so it does not stop at the first >. It races to the end of the line, then backtracks until it finds a > that lets the pattern finish. The nearest > from the end is the final one, so the match is the entire string <b>bold</b>, not just <b>. That is almost never what people want.

Text:     <b>bold</b>
Pattern:  <.*>
Match:    <b>bold</b>     (the whole thing, greedy)

This is the single most common regex surprise. The engine did exactly what you told it: match a <, then as many of any character as possible, then a >. “As many as possible” is the operative phrase.

How do I make a quantifier lazy?

Add a ? immediately after the quantifier, giving *?, +?, ??, or {n,m}?. A lazy quantifier does the opposite of greedy: it matches as little as possible, then expands one character at a time only when the rest of the pattern would otherwise fail.

Rewrite the previous pattern as <.*?>. Now the .*? starts by matching nothing, then the engine tries to match > right after the opening <. That fails at b, so the lazy quantifier reluctantly accepts one character, then tries again, and keeps going until it reaches the first >. The match is just <b>, which is what you intended, and a global search finds </b> as a separate second match.

Text:     <b>bold</b>
Pattern:  <.*?>
Match 1:  <b>             (lazy stops at first >)
Match 2:  </b>

Lazy quantifiers are ideal when you want the shortest run up to a delimiter. That said, an often cleaner and faster alternative is a negated character class: <[^>]*> matches a <, then any characters that are not >, then a >. It reaches the same result without relying on backtracking at all, because it simply cannot cross a > in the first place. When a clean negated class is available, it is usually the better tool.

How does backtracking connect greedy and lazy?

Backtracking is the engine’s mechanism for trying alternatives when a match attempt fails, and both greedy and lazy quantifiers are defined in terms of it, they just start from opposite ends. A regex engine matches by moving forward and, whenever it hits a dead end, stepping back to the most recent quantifier that still has untried options and adjusting how much it consumed.

A greedy quantifier first grabs the maximum and backtracks by giving characters back one at a time. A lazy quantifier first grabs the minimum and backtracks by taking characters one at a time. In both cases the goal is the same overall match; the difference is which candidate they try first, and therefore which match they settle on when several are possible. On text where only one match exists, greedy and lazy produce identical results but may differ in how much work they do to get there.

Greedy .*   on "abcXYZ" with pattern .*Z
  step 1: match "abcXYZ", need Z, fail (end of string)
  step 2: give back "Z", match "abcXY", next char is Z -> success
Lazy .*?   on same input with .*?Z
  step 1: match "", next char is 'a' not Z, expand
  ... expand until "abcXY", next char Z -> success

Can you show worked examples side by side?

Yes. The table below runs several patterns against fixed input so you can see greedy and lazy diverge. Assume a single match (not global) unless noted, and read the match column as the exact substring captured.

Input Pattern Matches Why
"a" "b" "c" ".*" "a" "b" "c" Greedy runs to the last quote
"a" "b" "c" ".*?" "a" Lazy stops at the first closing quote
<p><a></a> <.+> <p><a></a> Greedy + spans everything
<p><a></a> <.+?> <p> Lazy +? takes the smallest tag
2038-01-19 d{2,4} 2038 Greedy range grabs up to four digits
2038-01-19 d{2,4}? 20 Lazy range grabs the minimum two

Two takeaways fall out of this. First, greedy and lazy only differ when the text allows more than one valid match length; pick the one whose “first choice” matches your intent. Second, quantifiers like {2,4} are greedy too, and adding ? makes even a bounded range prefer its lower bound. Testing patterns against real sample text is the fastest way to build intuition, and an interactive regex tester that highlights each match as you type lets you watch greedy and lazy behavior change in real time without guessing.

What is catastrophic backtracking and how do I avoid it?

Catastrophic backtracking happens when a pattern can split the same input in exponentially many ways, so a string that ultimately fails to match forces the engine through an enormous number of attempts, and the regex appears to hang. It arises most often from nested quantifiers where the inner and outer repetitions overlap in what they can consume.

A textbook trigger is (a+)+$ applied to a long run of a characters followed by a non-a, such as aaaaaaaaaa!. The inner a+ and outer + can partition the a’s in a combinatorial explosion of ways, and because the trailing ! can never satisfy $, the engine tries them all before giving up. Add more a’s and the time roughly doubles each time, the signature of exponential blowup.

Dangerous:  (a+)+$        on "aaaaaaaaaaaaaaaaaaaa!"
Safer:      a+$           (no nested quantifier)
Safer:      (?:a)+$       still linear, no overlapping repeats

To avoid it, do not nest quantifiers that match the same characters, prefer a specific negated character class over a permissive .*, and anchor patterns so the engine can fail fast. Where your regex flavor supports them, atomic groups or possessive quantifiers prevent the backtracking that causes the blowup by forbidding the engine from giving characters back once taken. The general discipline is the same one that keeps regexes readable: match precisely what you mean, lazily or with a negated class when you want the shortest run, and test against realistic input, including the strings you expect to fail, so a pathological case surfaces on your machine rather than in production.

Frequently asked questions

What is a regex quantifier?

A quantifier controls how many times the preceding element may repeat. The core set is * (zero or more), + (one or more), ? (zero or one), and {n,m} for an explicit range.

What does greedy mean in regex?

Greedy quantifiers match as much text as possible, then give characters back through backtracking only if the rest of the pattern cannot otherwise match. Standard quantifiers are greedy by default.

How do I make a quantifier lazy?

Add a question mark after it: *? +? ?? and {n,m}?. A lazy quantifier matches as little as possible, expanding only when forced to let the overall match succeed.

Why does my regex match too much?

A greedy quantifier like .* stretches to the last possible match rather than the first. Switch to lazy .*? or use a negated character class to stop at the nearest delimiter.

What is catastrophic backtracking?

When nested quantifiers create exponentially many ways to split the input, a non-matching string can force a huge number of backtracking attempts, making the regex hang. Restructure the pattern to avoid it.