Concept
Lazy vs. Greedy Quantifiers
Greedy quantifiers (*, +) consume as much as possible before backtracking. Lazy quantifiers (*?, +?) consume as little as possible.
The default is greedy
By default, + and * match as many characters as they can while still allowing the overall pattern to succeed. On <.+>, the .+ will race to the end of the string and then backtrack one character at a time until the final > is found — often capturing far more than intended.
Greedy — over-captures
Try this/<.+>/Input
<b>bold</b> and <i>italic</i>Result
Match: <b>bold</b> and <i>italic</i> (the whole thing)Adding ? makes the quantifier lazy
+? and *? switch to lazy (also called non-greedy or reluctant) matching. They match as few characters as possible, expanding only when needed to complete the overall match.
Lazy — matches each tag separately
Try this/<.+?>/gInput
<b>bold</b> and <i>italic</i>Result
Matches: <b>, </b>, <i>, </i>When to use which
Default to greedy when you want the longest possible match — numeric sequences, whole-word captures. Switch to lazy when you're matching to a specific terminator that could appear multiple times, like HTML tags, quoted strings, or multi-line comments.
Alternative: a negated character class
<[^>]+> is often better than <.+?> for tags — it directly says 'anything that isn't >.' It's faster (no backtracking) and clearer about intent. Use lazy quantifiers when the terminator can't be expressed as a single character set.
Related patterns
HTML Tag Matcher
Match paired HTML tags and capture the tag name and inner content using a back-reference.
/<([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>([\…/gHTML Comment
Match HTML comments, including multi-line comments and empty ones.
/<!--[\s\S]*?-->/gMarkdown Link
Match Markdown links [link text](url) and capture both the display text and the URL.
/\[([^\]]+)\]\(([^)]+)\)/gMarkdown Heading
Matches markdown ATX-style headings (# through ######).
/^(#{1,6})\s+(.+)$/gm