← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Greedy vs Lazy Quantifiers

Regex PHP 5.3+ Beginner
debt(d8/e1/b3/t7)
d8 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'silent in production until users hit it' (d9), scored d8 because no PHP-specific tool flags greedy-vs-lazy mismatches; the bug typically passes tests with simple inputs and only surfaces when production data contains repeated delimiters. Careful code review can sometimes catch it.

e1 Effort Remediation debt — work required to fix once spotted

Closest to 'one-line patch or single-call swap' (e1) — per quick_fix, adding ? to the quantifier (.* → .*?) or switching to a negated character class is a single-token edit within one pattern.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3) — regex patterns are localised to where they're used; greedy/lazy choice doesn't shape system architecture, but applies broadly across web/cli contexts wherever regex is used, slightly above minimal commitment.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7) per the misconception: developers assume lazy is universally safer/more correct, and greedy .* silently captures too much across repeated delimiters. The 'obvious' fix (switch to lazy) is itself often wrong vs. a negated character class — contradicts intuition.

About DEBT scoring →

Also Known As

greedy matching lazy matching reluctant quantifier non-greedy minimal matching

TL;DR

Greedy quantifiers (*, +, ?) match as much as possible; lazy quantifiers (*?, +?, ??) match as little as possible — the difference determines which text is captured when multiple matches are possible.

Explanation

By default, quantifiers are greedy — they consume the maximum number of characters that still allow the overall pattern to match. Adding ? after a quantifier makes it lazy — it consumes the minimum. For example, /<.+>/ on '<b>bold</b>' matches the entire string '<b>bold</b>' (greedy); <.+?> matches only '<b>' (lazy). This distinction is critical when parsing HTML-like content, extracting substrings between delimiters, or matching the shortest possible token in a string with repeated delimiters. PHP uses PCRE which supports both modes for *, +, ?, and {n,m}.

Common Misconception

Lazy quantifiers are always safer or more correct than greedy ones. Neither is universally correct — the right choice depends on what you are trying to match. Lazy matching on .*? inside a complex pattern can produce unexpected empty matches or match too little. For extracting content between known delimiters, a negated character class ([^<]+) is often more precise and faster than lazy matching (.*?).

Why It Matters

The greedy/lazy distinction is responsible for a large proportion of regex bugs in PHP code. A pattern that works correctly on simple test strings frequently misbehaves on real data that contains the delimiter character more than once. Understanding this prevents silent data extraction errors where your regex captures the wrong substring — a particularly insidious bug because it often passes testing but fails on edge-case production data.

Common Mistakes

  • Using .* to match content between delimiters when [^delimiter]* is more precise and performs better.
  • Testing regex only on minimal inputs — greedy bugs appear when the input contains multiple instances of the delimiter.
  • Mixing greedy and lazy quantifiers in the same pattern without understanding how backtracking interacts between them.
  • Assuming lazy quantifiers prevent catastrophic backtracking — they reduce it in some cases but can introduce different backtracking patterns.

Code Examples

✗ Vulnerable
// Greedy — matches from first < to LAST >
$html = '<b>bold</b> and <i>italic</i>';
preg_match('/<.+>/', $html, $m);
// $m[0] = '<b>bold</b> and <i>italic</i>' — wrong
✓ Fixed
// Lazy — matches from < to next >
preg_match('/<.+?>/', $html, $m);
// $m[0] = '<b>' — correct

// Even better — negated class, no backtracking
preg_match('/<[^>]+>/', $html, $m);
// $m[0] = '<b>' — faster and more precise

Added 23 Mar 2026
Edited 5 Apr 2026
Views 93
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 3 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W 2 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 3 pings T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F
No pings yet today
No pings yesterday
Amazonbot 9 Scrapy 9 ChatGPT 7 Ahrefs 6 Google 5 Bing 5 SEMrush 5 Perplexity 4 PetalBot 3 Twitter/X 2 Brave Search 2 Applebot 2 Meta AI 1
crawler 56 crawler_json 4
🧱 FUNDAMENTALS — new to this? Start with the ground floor.
Regex general A regex (regular expression) is a pattern you write to search, match, or replace text. It's like a super-powered find-and-replace that can match flexible patterns instead of exact words.

Regex turns hours of manual text hunting into a single line of code. From form validation to log parsing to data cleanup, pattern matching is everywhere in real-world programming.

💡 When a regex misbehaves, check if your special characters need escaping — dots, brackets, and slashes often do.

Ask Codex about Regex →
DEV INTEL Tools & Severity
🔵 Info ⚙ Fix effort: Low
⚡ Quick Fix
Add ? after any quantifier to make it lazy: .* becomes .*?, .+ becomes .+? — or use a negated character class [^x]+ for content that should not contain character x
📦 Applies To
PHP 5.3+ web cli


✓ schema.org compliant