Regex Non-Greedy vs Greedy Matching — Side-by-Side Examples

One of the most common regex surprises is a quantifier matching way more text than expected. It usually comes down to greedy versus non-greedy (lazy) behavior. This guide shows exactly how each one works with side-by-side match results so you can predict the behavior instead of guessing.

Greedy Quantifiers (Default Behavior)

By default, quantifiers like *, +, and {n,m} are greedy: they try to consume as much of the string as possible first, then backtrack one character at a time until the rest of the pattern can match.

const html = '<b>bold</b> and <i>italic</i>';

// Greedy: .* grabs everything up to the LAST '>'
html.match(/<.*>/)[0];
// '<b>bold</b> and <i>italic</i>'  — spans the whole string!

Non-Greedy (Lazy) Quantifiers

Adding a ? after a quantifier makes it lazy: it matches as few characters as possible, only expanding when required for the overall pattern to succeed.

const html = '<b>bold</b> and <i>italic</i>';

// Non-greedy: .*? stops at the FIRST '>'
html.match(/<.*?>/)[0];
// '<b>'  — stops as soon as possible

// With the global flag, you get every tag separately
html.match(/<.*?>/g);
// ['<b>', '</b>', '<i>', '</i>']

Side-by-Side Comparison Table

Input:    "aaa"
Pattern:  a+   (greedy)   → matches "aaa"  (all three a's)
Pattern:  a+?  (lazy)     → matches "a"    (just one a)

Input:    "start...middle...end"
Pattern:  start(.*)end     (greedy)  → captures "...middle...end" minus "end" itself... actually captures everything up to the LAST "end"
Pattern:  start(.*?)end    (lazy)    → captures "..." up to the FIRST "end"

Input:    '"first" and "second"'
Pattern:  ".*"   (greedy)  → matches '"first" and "second"'  (whole thing)
Pattern:  ".*?"  (lazy)    → matches '"first"'  (just the first quoted string)

All the Lazy Variants

  • *? — zero or more, as few as possible
  • +? — one or more, as few as possible
  • ?? — zero or one, prefers zero
  • {n,m}? — between n and m, prefers n

A Better Alternative — Negated Character Classes

For extracting content between two known delimiters, a negated character class is often more precise and faster than a lazy dot, because it can't accidentally "jump over" a closing delimiter of a different kind:

const html = '<b>bold</b>';

// Lazy dot — works, but .*? can still match across unintended characters
html.match(/<(.*?)>/)[1];  // 'b'

// Negated character class — explicitly "anything but >"
html.match(/<([^>]*)>/)[1]; // 'b'  — same result here, but more robust on messy input

[^>]* is generally the preferred idiom over .*? when you know the exact character that should stop the match — it is both clearer intent and avoids some pathological backtracking cases that lazy dots can trigger on malformed input.

When to Choose Which

  • Use greedy when you want the longest possible match, e.g. matching an entire multi-line comment block
  • Use lazy when you want the shortest possible match between two known delimiters, e.g. quoted strings, single HTML tags
  • Prefer a negated character class ([^x]*) over a lazy dot when the stopping character is known and fixed
  • Always test against your actual data — greedy vs lazy mistakes are one of the most common sources of subtle bugs in text-processing code

Frequently Asked Questions

What is the difference between greedy and non-greedy regex matching?

A greedy quantifier like .* matches as many characters as possible, then backtracks if needed. A non-greedy (lazy) quantifier like .*? matches as few characters as possible, expanding only if the rest of the pattern requires it.

How do I make a regex quantifier non-greedy?

Add a question mark after the quantifier: *? for zero-or-more, +? for one-or-more, ?? for zero-or-one, and {n,m}? for a bounded range.

Is non-greedy matching always slower than greedy matching?

Not necessarily. Performance depends on the input and how much backtracking each approach needs. In many cases a well-anchored non-greedy pattern is actually faster because it stops as soon as a match is found, rather than consuming the whole string and backtracking.

Try the Free AI Regex Generator

Describe any validation rule in plain English and get a working regex instantly — no signup required.

Related articles