[ 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-06-04

This is the single most useful database habit to get right in Laravel, because the wrong choice is the N+1 query, and the N+1 is the most common reason a Laravel page is slow. The mechanics are simple once you've seen them, and the fix is usually one method call. The hard part is noticing you needed it.

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 the models are already in hand: load()

with() works when you control the query. Sometimes you don't — a collection gets passed into a method, or you fetched it before you knew you'd need the relationship. load() is the after-the-fact version:

php
$users = User::take(100)->get();   // already fetched, no relations

// ...later, once you know you need them:
$users->load('posts');             // one batched query, not 100

// inside reusable code, prefer loadMissing — it skips models
// that already have the relation loaded:
$users->loadMissing('posts');

Rule of thumb: with() when you're writing the query, load() or loadMissing() when the models already exist.

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