Skip to content

/glossary/regex/greedy-vs-lazy-quantifiers

Greedy vs lazy quantifiers in regex

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · grep (GNU grep) 3.0updated 2026-10-20
tool
Regex
versions
PCRE, Python, JavaScript
os
any

Quantifiers (*, +, ?, {n,m}) are greedy by default: they match as many characters as possible, then give some back (backtrack) only if the rest of the pattern would fail otherwise. Adding ? makes them lazy: *?, +?, ??, {n,m}? match as few characters as possible and take more only when needed.

The classic example

bash
node - <<'EOF'
const html = '<b>bold</b> and <i>italic</i>';
console.log("greedy <.+>   ", html.match(/<.+>/g));
console.log("lazy   <.+?>  ", html.match(/<.+?>/g));
console.log("class  <[^>]+>", html.match(/<[^>]+>/g));
const csv = '"a","b,c","d"';
console.log("greedy \".*\"  ", csv.match(/".*"/g));
console.log("lazy   \".*?\" ", csv.match(/".*?"/g));
EOF

Output

greedy <.+>    [ '<b>bold</b> and <i>italic</i>' ]
lazy   <.+?>   [ '<b>', '</b>', '<i>', '</i>' ]
class  <[^>]+> [ '<b>', '</b>', '<i>', '</i>' ]
greedy ".*"   [ '"a","b,c","d"' ]
lazy   ".*?"  [ '"a"', '"b,c"', '"d"' ]

The greedy <.+> runs to the last > on the line and returns one big match. The lazy <.+?> stops at the first > it can. A negated class, <[^>]+>, gives the same result as the lazy form without backtracking, and is usually the better choice.

In short

GreedyLazyBehaviour
**?zero or more: as many / as few as possible
++?one or more
???zero or one
{2,5}{2,5}?between 2 and 5

Laziness changes which match is found, not whether one is found. For parsing real HTML, JSON or CSV, use a parser; regexes break on nesting and escaping.

Sources