[ GLOSSARY ]

Eager vs lazy loading in Eloquent

QUICK ANSWER

What's the difference between eager and lazy loading in Laravel Eloquent?

Lazy loading fetches a relationship on demand, firing one query per access — which becomes N+1 when accessed inside a loop. Eager loading uses with() to fetch all related records up front in one additional query with WHERE IN, regardless of parent count. Eager is faster for collections; lazy is fine for single parents. Use preventLazyLoading in development to catch N+1 as a hard error.

Updated · 2026-04-13

Lazy loading, by default

Eloquent loads relationships on demand. Accessing $user->posts fires a query at the moment of access:

php
$users = User::take(100)->get();  // 1 query

foreach ($users as $user) {
    echo $user->posts->count(); // 1 query per user — 100 total
}

// Total: 1 + 100 = 101 queries → N+1

Eager loading with with()

php
$users = User::with('posts')->take(100)->get();  // 2 queries total

foreach ($users as $user) {
    echo $user->posts->count(); // no query fires — already loaded
}

// Total: 2 queries, regardless of user count

Under the hood: one SELECT * FROM users LIMIT 100, then one SELECT * FROM posts WHERE user_id IN (1, 2, 3, ..., 100). Eloquent wires the posts back to their users in-memory.

Nested relationships

php
// Load users + their posts + each post's comments + each comment's author
User::with('posts.comments.author')->get();

// 4 queries total:
// 1. SELECT * FROM users
// 2. SELECT * FROM posts WHERE user_id IN (...)
// 3. SELECT * FROM comments WHERE post_id IN (...)
// 4. SELECT * FROM users WHERE id IN (...)  -- comment authors

Four queries for any number of rows. Beats N+1-squared-squared-squared, which would be thousands.

Constraining eager loads

php
// Only eager-load published posts
User::with(['posts' => fn ($q) => $q->where('published', true)])->get();

// Only specific columns (reduces memory)
User::with('posts:id,user_id,title')->get();

// Load counts without the rows themselves
User::withCount('posts')->get();

preventLazyLoading — catch N+1 in dev

app/Providers/AppServiceProvider.php

php
use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    Model::preventLazyLoading(! $this->app->isProduction());
}

In non-production, any lazy-loaded relationship throws LazyLoadingViolationException. You're forced to either eager-load or explicitly $user->loadMissing('posts'). Catches N+1 as a bug during development rather than as a slow page in production.

Don't enable in production — a missed eager-load becomes a 500 for real users instead of a slow page.

When lazy loading is actually fine

On a single model, not a collection: $user = User::find(1); $user->posts; fires one query. No N+1. The problem only materializes with loops over collections.

Frequently asked questions

What's the difference between eager and lazy loading in Laravel?

Lazy loading fetches related models on demand — each access to a relationship fires a separate query. Eager loading fetches the relationship upfront with a single extra query using WHERE IN. Eager loading is faster for most cases because it avoids the N+1 problem; lazy loading is simpler and fine when you only access one parent's relations.

How do I eager load in Laravel?

Use with() on the query. User::with('posts')->get() loads all users and their posts in two queries regardless of user count — one SELECT FROM users, one SELECT FROM posts WHERE user_id IN (...). Without with(), accessing $user->posts on each user fires one query per user, which is N+1.

Can I eager load nested relationships?

Yes. User::with('posts.comments.author')->get() loads users + posts + comments + authors in four queries total, regardless of how many rows. Be careful with deep nesting — it's still better than N+1 but the query set can return a lot of data. At some point you want to denormalize or paginate.

What's preventLazyLoading?

A Laravel feature that throws an exception whenever lazy loading happens — forcing you to explicitly eager load. Call Model::preventLazyLoading() in AppServiceProvider::boot() in local/staging environments. Catches N+1s as development-time errors instead of as production performance bugs.

When is lazy loading actually fine?

When you're working with a single parent model — not a collection. If you loaded one $user and access $user->posts, that's one query, not N+1. If you loaded a collection of users and access ->posts on each one, that's N+1. Eloquent doesn't care about the distinction; you do.

How does NightOwl detect eager loading opportunities?

NightOwl records every query per request. When the dashboard shows a request fired 1,000 SELECT FROM posts WHERE user_id = ? queries, that's a classic N+1 waiting to be fixed with with('posts'). The query pattern grouping surfaces these across production traffic, not just local dev.

PRICING

Flat pricing. No event caps. No per-seat fees.

14-day free trial, no credit card. Your PostgreSQL, your data.

HOBBY

$5 /month

1 app · 14 days lookback · all Laravel events

TEAM

$15 /month

Up to 3 connected apps · unlimited environments · all Laravel events

AGENCY

$69 /month

Unlimited apps · unlimited agent instances · same flat rate at any traffic