Could we help you? Please click the banners. We are young and desperately need the money
If you've been working with Laravel for a while, you've probably used Eloquent relationships to connect your models. But there's a common performance trap that catches almost every junior developer at some point: the N+1 query problem. The way you load your relationships — using with() or load() — makes a bigger difference than you might think. In this post, we'll break down the difference between these two methods in plain language, show you exactly when to use each one, and help you write Laravel code that's both clean and fast.
By default, Laravel loads relationships lazily — meaning it only fetches related data when you actually access it. This sounds convenient, but it causes a serious problem when you're looping over a collection.
Imagine you have 100 posts, and each post has an author. If you loop through the posts and access $post->author inside the loop, Laravel fires a separate database query for every single post. That's 1 query for the posts + 100 queries for the authors = 101 queries. This is the N+1 problem.
Eager loading solves this by fetching all the related data upfront in just 2 queries, no matter how many records you have. Both with() and load() are eager loading tools — but they work at different points in time.
with() is used before you retrieve your models from the database. You chain it onto a query builder call, and Laravel bundles the relationship loading into the initial query execution.
// Without eager loading (causes N+1 problem)
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // fires a new query every iteration!
}
// With eager loading using with()
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name; // no extra queries — data is already loaded
}
$posts = Post::with(['author', 'comments', 'tags'])->get();
// Load comments and each comment's author
$posts = Post::with('comments.author')->get();
load() does the same thing as with() — it fetches related data in a single additional query — but it works after you've already retrieved your models. You call it on an existing collection or model instance.
// You already have the posts (fetched earlier, passed in, etc.)
$posts = Post::all();
// Now eager load the relationship on the existing collection
$posts->load('author');
foreach ($posts as $post) {
echo $post->author->name; // no N+1 — loaded efficiently
}
$post = Post::find(1);
// Load a relationship onto the already-fetched model
$post->load('comments');
$posts->load(['author', 'comments.author']);
| Feature | with() |
load() |
|---|---|---|
| When it runs | Before the query executes | After models are already fetched |
| Called on | Query builder / model class | Model instance or collection |
| Prevents N+1? | Yes | Yes |
| Requires re-querying? | No — part of the original query | No — runs a new query on existing data |
| Use when | You know what you need upfront | You need to load relationships conditionally or after the fact |
| Chainable on query? | Yes | No |
Here's a scenario where load() is the right tool. Suppose you have an API endpoint that returns posts, but you only want to load comments when a query parameter is present:
public function index(Request $request)
{
$posts = Post::with('author')->get();
// Conditionally load comments only if the client requests them
if ($request->boolean('include_comments')) {
$posts->load('comments');
}
return PostResource::collection($posts);
}
Using with() here would mean always loading comments, even when the client doesn't need them. With load(), you keep the query lean by default and only add the extra data when it's actually requested.
Laravel also provides a loadMissing() method. It works exactly like load(), but it only fires a query if the relationship hasn't been loaded already. This is useful for avoiding redundant queries when you're not sure if a relationship is already present.
// Only loads 'author' if it's not already loaded on the model
$post->loadMissing('author');
This is particularly helpful inside API resources or when passing models through multiple layers of your application.
// Wrong: load() inside a loop = N+1 all over again
foreach ($posts as $post) {
$post->load('author'); // fires a query every iteration!
}
// Correct: load() on the collection before the loop
$posts->load('author');
foreach ($posts as $post) {
echo $post->author->name;
}
The rule of thumb is straightforward:
with()load()loadMissing()Both methods prevent the N+1 query problem equally well. The difference is purely about timing and context. As you grow as a Laravel developer, you'll start to instinctively reach for the right tool depending on where in your code you are and what data you already have available.
Got questions or a tricky real-world scenario? Drop it in the comments — happy to help you figure out the best approach.