/^/

Lesson 17 of 28

Lazy Matching

Learn the lazy (non-greedy) quantifiers *?, +?, ??, and {n,m}? which match as little text as possible instead of as much.

6 min read

In the last lesson, greedy quantifiers matched as much as possible before backtracking. Lazy (also called non-greedy or reluctant) quantifiers flip that instinct: they match as little as possible, only taking more if the rest of the pattern absolutely requires it.

You write a lazy quantifier by adding a ? right after a greedy one:

Greedy Lazy Meaning
* *? 0 or more, as few as possible
+ +? 1 or more, as few as possible
? ?? 0 or 1, prefers 0
{n,m} {n,m}? between n and m, prefers n

Fixing the HTML pitfall

Let’s revisit the exact example from the greedy lesson — matching HTML tags — but this time with a lazy quantifier:

const html = "<b>bold</b> and <i>italic</i>";
const matches = html.match(/<.+?>/g);
console.log(matches); // ["<b>", "</b>", "<i>", "</i>"]
<b>bold</b> and <i>italic</i>
4 matches

The difference is dramatic. With .+ (greedy), the engine matched the entire string in one go, from the first < to the very last >. With .+? (lazy), the engine takes the smallest number of characters that lets a > follow — so at the first <, it tries one character (b), immediately finds a >, and stops. That gives four clean, individual tag matches instead of one sprawling one.

Lazy digit chunks vs. greedy digit chunks

Recall the digit example from the previous lesson: \d{2,4} (greedy) against "123456 78 9" produced ["1234", "56", "78"], since each match grabbed as many digits as allowed. Now watch what the lazy version does to the same text:

const matches = "123456 78 9".match(/\d{2,4}?/g);
console.log(matches); // ["12", "34", "56", "78"]
123456 78 9
4 matches

Since nothing after the quantifier forces it to expand, {2,4}? always settles for exactly the minimum of 2 digits at each position — chunking 123456 into 12 and 34 and 56, rather than greedy’s 1234 and 56.

Tip

Lazy doesn’t mean “correct” and greedy doesn’t mean “wrong” — they’re just different strategies for an ambiguous match. The right choice depends entirely on what you’re trying to extract. For HTML-tag-shaped text, lazy is usually closer to what you want; for “grab the longest possible number,” greedy is exactly right.

Lazy quantifiers can still backtrack forward

It’s a common misconception that lazy quantifiers never take more than the minimum. They will expand, one character at a time, if the minimum isn’t enough to satisfy the rest of the pattern:

/a.*?c/.exec("abbbc"); // matches "abbbc" — has to expand until it hits "c"

Here .*? starts by trying zero characters, then one, then two, and so on — expanding only because there’s no c to be found until the end.

Lazy vs. greedy on quoted strings

Given the text 'first' and 'second', compare what /'.*'/ (greedy) matches versus what /'.*?'/ (lazy) matches.

Show solution

The greedy version /'.*'/ matches the entire string 'first' and 'second' — from the first ' all the way to the very last ', because .* grabs everything and only backtracks as far as needed. The lazy version /'.*?'/ matches just 'first' — it stops at the very first closing ' it can find. This is exactly why lazy quantifiers are the standard choice for matching quoted strings or paired delimiters.

What’s next

You’ve now mastered both matching strategies quantifiers can use. Next up: regex flags — the g, i, m, s, u, and y modifiers that change how an entire pattern behaves, from case sensitivity to multiline anchors.

Quick check

Which symbol turns the greedy + quantifier into its lazy equivalent?

Applying <.+?> with the global flag to "<b>bold</b> and <i>italic</i>" produces which matches?