Lesson 06: Greedy vs lazy, then build one
Lesson objectives:
- Explain why
.+is greedy and matches as much as possible.- Make a quantifier lazy with
?so it matches as little as possible.- Write and verify a real validator-and-extractor in a live console.
Prerequisites: Lessons 01-05 (the whole toolkit) | Previous << 05
Your pattern matched — but it grabbed way too much
You write /".+"/ to grab a quoted word, run it on say "hi" and "bye", and instead of "hi" you get back "hi" and "bye" — the whole span from the first quote to the last. The pattern isn't broken; it's doing precisely what + was always going to do. This is the single most famous regex surprise, and it's the last concept standing between you and reading real patterns: quantifiers are greedy by default. Once you can see the greed — and switch it off — the .+? you've been pasting for years finally makes sense. Then we put all six lessons together and build something real.
Explanation
By default, quantifiers like * and + are greedy: they try to match as many times as possible1. A greedy + tells the engine to repeat the preceding token as often as it can2. So /".+"/ on say "hi" and "bye" doesn't stop at the first closing quote — the .+ keeps eating characters (including the middle quotes, since . matches them) all the way to the last quote it can, then backs up just enough to let the final " match.
To reverse it, put a ? right after the quantifier. That makes it lazy (non-greedy): it stops as soon as it has the minimum number of matches1. A lazy .+? repeats the dot as few times as possible2. Now the engine takes one character, checks whether the rest of the pattern can match, and only keeps going if it must — so it stops at the first closing quote.
The textbook demonstration is a pair of adjacent bracketed tags. Against the string [one][two], greedy /\[.+\]/ swallows the whole thing, while lazy /\[.+?\]/ grabs just the first tag. (The brackets are escaped with \ because [ and ] are the character-class metacharacters.)
Read ? carefully here, because it now has two jobs you've met. On its own, after a token, ? is the quantifier "0 or 1" from Lesson 03. Directly after another quantifier (+?, *?, {2,5}?), it is the lazy modifier. Same character, two roles — which one it plays depends on what sits to its left.
Worked example (follow along)
You want the text of the first HTML tag in a string, not everything up to the last one. Watch greedy fail, then fix it with lazy:
Step by step: <.+> starts at the first "<", and greedy .+ runs as far right as it can while still leaving a ">" to match — that's the last ">", so element 0 is the entire string. Adding the ? — <.+?> — makes .+? take as little as possible: after the first "<", it consumes "b" and immediately finds it can satisfy the closing ">", so it stops, giving "<b>". Same characters, opposite appetite.
Your turn (faded example)
You want to grab the first parenthesized note in total (net) and (gross), i.e. "(net)", not "(net) and (gross)". Fill in the one character that makes the quantifier lazy:
Answer: the blank is ?, giving /\(.+?\)/. The \( and \) are escaped literal parentheses (escaped because parentheses are the grouping metacharacters). Greedy .+ would run to the last ")", capturing "(net) and (gross)"; the ? makes .+? stop at the first ")", so you get "(net)". This "escape the delimiters, make the middle lazy" shape is exactly how you extract the first delimited chunk out of a longer string.
Summary + what's next
Quantifiers are greedy by default — .+ matches as much as it can — and a ? right after a quantifier makes it lazy, matching as little as possible. That single character is the difference between grabbing the first delimited chunk and swallowing the whole line. You've now assembled the entire pattern language: literals, character classes, quantifiers, anchors, groups, alternation, and greedy-vs-lazy. When you meet an unfamiliar regex now, you can read it token by token instead of pasting it and hoping. The mentor-action below turns that around — hand the agent the validator you just built and let it drill you on every token.
Footnotes
-
MDN: Quantifiers — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Quantifiers ↩ ↩2
-
regular-expressions.info: Repetition (greedy and lazy) — https://www.regular-expressions.info/repeat.html ↩ ↩2
Exercises
This is the famous one. In your console, define const s = "<p><span>hi</span></p>";. Run s.match(/<.+>/) and s.match(/<.+?>/). Predict each element 0 first, then explain the difference in one sentence.
Open your Node REPL or DevTools console and build a date tool from scratch, using pieces from every lesson. Do not paste one from the internet — write it, then verify it.
- Write a validator that is true only when the entire string is an ISO date
YYYY-MM-DD(four digits, dash, two digits, dash, two digits, and nothing else). - Write an extractor that pulls the year, month, and day into separate captured groups.
- Verify both against these strings, predicting each result first:
"2024-01-15"(valid),"2024-1-15"(invalid — one-digit month), and"see 2024-01-15 today"(the validator must reject it, but the extractor should still be able to find the date inside).