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

Eager vs Lazy Loading — When to Use Each

Performance PHP 5.0+ Intermediate
debt(d5/e3/b5/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints.tools list includes laravel-debugbar, doctrine-profiler, and clockwork — all specialist profiling/debugging tools rather than default linters or compilers. The code_pattern notes '50+ identical queries' visible in these tools, meaning the problem is silent in normal development without these tools enabled, but detectable with them. Not quite d7 because these tools are relatively accessible in PHP ORMs.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix confirms switching to eager loading with `with()` for relations accessed in loops — a targeted change within one component or query layer. It's not a one-liner across the board (e1) because you must identify all affected queries and potentially adjust selects, but it doesn't span multiple architectural layers.

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

Closest to 'persistent productivity tax' (b5). The term applies_to all contexts (web, cli, queue-worker) and the common_mistakes note 'ORM lazy loading enabled globally' causes hidden N+1 in every list view. This means a poor default choice imposes an ongoing tax across many features and work streams, but it doesn't redefine the system's shape — it's a recurring pattern decision rather than an architectural constraint.

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

Closest to 'serious trap' (t7). The misconception field explicitly states 'Always prefer eager loading to avoid N+1 queries' — a plausible, well-intentioned rule that is actively wrong in many real cases (over-fetching unused relations). This contradicts the common advice given to developers learning about N+1, making it a serious trap: the 'obvious fix' (always eager load) is itself a source of a different performance problem.

About DEBT scoring →

Also Known As

eager vs lazy loading strategy ORM fetch strategy

TL;DR

Eager loading fetches related data upfront in one query; lazy loading defers it until accessed — the wrong default causes N+1 or over-fetching.

Explanation

Eager loading (JOIN FETCH in Doctrine, with() in Eloquent) retrieves related entities in the same query as the parent — optimal when you know you'll use the association. Lazy loading defers fetching until the property is accessed for the first time — convenient for occasionally-needed associations, but silently causes N+1 when accessed in a loop. Doctrine defaults to LAZY; Eloquent defaults to lazy (without with()). Decision rules: if you always display the association on a list page — eager load it. If the association is rarely used (only on detail pages) — lazy is acceptable with careful monitoring. Avoid lazy loading entirely in performance-critical loops; use batch loading (IN query) as a middle ground between one-per-row and one-JOIN-for-all.

Common Misconception

Always prefer eager loading to avoid N+1 queries. Eager loading fetches all related data upfront — if you load 500 posts but only access the author for 3, you fetched 497 authors unnecessarily. Choose based on actual access patterns, not a blanket rule.

Why It Matters

Choosing between eager and lazy loading is a performance decision — lazy loading is convenient but causes N+1; eager loading is explicit and efficient but can over-fetch if relationships are unused.

Common Mistakes

  • ORM lazy loading enabled globally — hidden N+1 queries in every list view with no warning.
  • Not measuring actual query counts — assuming the ORM is efficient without checking.
  • Blanket eager loading all relationships on every query — loads gigabytes of related data for pages that show one field.
  • Not using select() to limit columns when eager loading — fetches full rows when only IDs or names are needed.

Code Examples

✗ Vulnerable
// Lazy default — N+1 problem
$posts = Post::all();         // 1 query
foreach ($posts as $post) {
    echo $post->author->name; // N queries
}
✓ Fixed
// Eager load when you know you'll need the relation
$posts = Post::with('author')->get(); // 2 queries, no matter how many posts

// Lazy eager load — load relation for an already-fetched collection
$posts->load('tags');         // 1 additional query for all tags

// Conditional eager loading
$posts = Post::when($includeComments, fn($q) => $q->with('comments'))->get();

// Laravel Debugbar or Telescope shows query count — aim for < 5 per request

Added 15 Mar 2026
Edited 22 Mar 2026
Views 101
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings F 1 ping S 0 pings S 0 pings M 1 ping T 1 ping W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 2 pings S 0 pings M 2 pings T 1 ping W 0 pings T 0 pings F 0 pings S
No pings yet today
No pings yesterday
Amazonbot 12 PetalBot 9 Ahrefs 7 Scrapy 7 Bing 6 ChatGPT 5 Google 4 Perplexity 3 Twitter/X 2 Applebot 2 Majestic 1 Meta AI 1 SEMrush 1 Unknown AI 1
crawler 56 crawler_json 5
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Low
⚡ Quick Fix
Default to lazy loading in Doctrine/Eloquent for development exploration, but switch to eager loading (with()) for any relation accessed in a loop or list view
📦 Applies To
PHP 5.0+ web cli queue-worker laravel doctrine eloquent
🔗 Prerequisites
🔍 Detection Hints
Loop accessing relation without eager load; Debugbar showing 50+ queries identical; Doctrine lazy proxy calls in loop
Auto-detectable: ✓ Yes laravel-debugbar doctrine-profiler clockwork
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: Class Tests: Update


✓ schema.org compliant