/^/

Lesson 16 of 28

Greedy Matching

Understand why quantifiers like *, +, and {n,} are greedy by default — matching as much text as possible before backtracking.

6 min read

By default, the quantifiers you already know — *, +, ?, and {n,m} — are all greedy. Greedy means: try to match as much text as possible first, and only give back (“backtrack”) characters if that’s the only way the rest of the pattern can succeed.

This default is convenient most of the time, but it can also produce surprising, over-eager matches if you’re not expecting it.

Greedy quantifiers grab the maximum

Consider \d{2,4} — “between 2 and 4 digits.” Applied repeatedly across a string, greedy behavior means each match takes as many digits as it’s allowed to, not the fewest.

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

Walk through it: starting at the first 1, the engine grabs the maximum of 4 digits (1234). The next attempt starts right after, at 56 — only 2 digits remain before the space, so it takes both. Then 78 matches the same way. The final lone 9 can’t match at all, because {2,4} requires at least 2 digits.

The classic HTML pitfall

The most famous example of greedy matching gone wrong is trying to match an HTML tag with .+:

const html = "<b>bold</b> and <i>italic</i>";
const match = html.match(/<.+>/);
console.log(match[0]);
// "<b>bold</b> and <i>italic</i>"  — the WHOLE string, not just "<b>"
<b>bold</b> and <i>italic</i>
1 match

Here’s why: .+ is greedy, so it first tries to consume everything to the end of the string. Then the engine needs a literal > to finish the match, so it backtracks one character at a time from the end — but the very last character in the string happens to be a > (from </i>), so backtracking stops immediately, right there. The result is one giant match spanning both tags instead of two small ones.

Watch out

This is the real reason “don’t parse HTML with a naive regex” is such common advice. It’s not that regex can’t match tag-like patterns — it’s that greedy quantifiers combined with a generic . will happily bridge across multiple tags unless you constrain them more carefully (which you’ll see in the next lesson).

Greedy quantifiers at a glance

Quantifier Meaning Greedy behavior
* 0 or more Matches as many as possible, backtracks if needed
+ 1 or more Matches as many as possible, backtracks if needed
{n,} n or more Matches as many as possible, backtracks if needed
{n,m} between n and m Matches up to m, backtracks toward n if needed

Predict the greedy match

Without running it yet, predict what /a.*b/ matches against the string "a1b2b3b".

Show solution

It matches "a1b2b3b" — the entire string. .* greedily grabs everything to the end, then backtracks character by character until it finds the last possible b that lets the pattern succeed, which is the final b in the string, not the first one.

What’s next

Now that you’ve seen greedy quantifiers eat more than you’d like, the next lesson introduces their opposite: lazy matching, using *?, +?, and friends, applied to this exact same HTML example so you can see the difference side by side.

Quick check

Applying the pattern \d{2,4} with the global flag to the text "123456 78 9", which matches are produced?

Why does the pattern <.+> match far more than a single HTML tag when applied to "<b>bold</b> and <i>italic</i>"?