Eager Loading & the N+1 Problem

The single most common Laravel performance bug — what causes it, and four ways to kill it.

The N+1 problem is the performance bug every Laravel developer meets. You load 50 posts (1 query), then loop and touch $post->author->name — and Laravel quietly runs 50 more queries, one per author. That is 51 queries where 2 would do.

The fix: eager load up front

// Lazy — 1 + N queries
$posts = Post::all();
foreach ($posts as $p) { echo $p->author->name; }

// Eager — 2 queries total
$posts = Post::with('author')->get();
foreach ($posts as $p) { echo $p->author->name; }

The full toolkit

  • with() — load relations up front; nest with dot notation and constrain with closures.
  • load() — eager-load onto models you already fetched.
  • withCount() / withSum() — aggregates without loading the rows.
  • loadMissing() — load only if not already present.

Best of all, you can make N+1 impossible to ship. Model::preventLazyLoading() throws the moment a relation is accessed without being eager-loaded.

Example

Example · php
<?php

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Throw on lazy loading everywhere EXCEPT production.
        Model::preventLazyLoading(! $this->app->isProduction());

        // In production, log the violation instead of crashing:
        Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
            logger()->warning("Lazy load: {$relation} on " . $model::class);
        });
    }
}

// --- A query that stays fast under load ---
$posts = \App\Models\Post::query()
    ->with([
        // Constrain + select only what the view needs.
        'author:id,name,avatar_path',
        'comments' => fn ($q) => $q->latest()->limit(3)->with('user:id,name'),
        'tags:id,name',
    ])
    ->withCount('comments')      // comments_count without loading them all
    ->withExists('likes as liked_by_me', fn ($q) => $q->whereKey(auth()->id()))
    ->latest()
    ->paginate(20);

foreach ($posts as $post) {
    echo $post->author->name;          // already loaded
    echo $post->comments_count;        // aggregate, no extra query
}

When to use it

  • A posts listing page eager loads author and tags for all posts in two extra queries instead of firing N queries inside the foreach loop.
  • A nested eager load like with('posts.comments') pre-fetches a user's posts and all comments on those posts in three total queries.
  • A conditional eager load uses load() on a model already retrieved to fetch a relationship only when a specific flag is set by the request.

More examples

with() to prevent N+1

with('author') tells Eloquent to load all related authors in one additional IN query after fetching the posts.

Example · php
// N+1: 1 + N queries
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // query per post
}

// Eager load: always 2 queries total
$posts = Post::with('author')->get();
foreach ($posts as $post) {
    echo $post->author->name; // no extra query
}

Nested and multi-relationship eager loading

Loads multiple relationships in one call; the closure on posts.comments constrains the eager load to the 3 most recent comments per post.

Example · php
$users = User::with([
    'profile',               // hasOne
    'posts',                 // hasMany
    'posts.tags',            // nested: tags for each post
    'posts.comments' => fn($q) => $q->latest()->limit(3),
])->paginate(20);

withCount and loadMissing

withCount() appends an aggregate column; loadMissing() fetches a relationship only if it has not already been eager loaded.

Example · php
// Add a comments_count column to each post without loading comments
$posts = Post::withCount('comments')->get();
echo $posts->first()->comments_count;

// Conditionally load a relationship on already-retrieved models
if ($request->boolean('include_author')) {
    $posts->loadMissing('author');
}

Discussion

  • Be the first to comment on this lesson.