The N+1 Query Problem in Laravel: What It Is and How to Fix It

by Giorgio Mazzei | May 1, 2026

Table of content

What is the N+1 problem?

The N+1 problem happens when your code fires one database query to fetch a list of records, then one additional query per record to load a related piece of data. If you have 100 posts, that’s 101 queries hitting your database for work that should take 2.

Here’s the classic example. You have a posts table and a users table. Every post belongs to an author (a user). You want to show a list of posts with their authors’ names:

With 50 posts, that innocent-looking loop fires 51 queries. With 500 posts, 501. The number of queries grows linearly with your data, which is exactly why apps that feel fine in development collapse under real-world load.

Why is it invisible

The N+1 problem doesn’t produce errors. Eloquent happily fires each query, Laravel returns the right data, and everything looks correct. The only symptom is slowness — and in development with 10 seed records, you won’t even notice it.

The fix: eager loading with with()

The solution is one word: with(). Instead of letting Eloquent lazily discover what relationships it needs inside the loop, you tell it upfront which relationships to load. Laravel then fetches everything in exactly two queries, no matter how many records you have.

That’s it. One change, from Post::all() to Post::with('author')->get(), and you go from 1+N queries down to exactly 2 — no matter the size of the dataset.

Eager loading doesn’t change what data you get back — it only changes whenand how the data is fetched. Your loop still accesses $post->author->name the same way.

The difference is that by the time the loop starts, all the authors are already sitting in PHP memory.

What happens under the hood

Understanding the two-query mechanism makes the fix click. When you call Post::with('author')->get(), Laravel does this internally:

Step 1 — It runs SELECT * FROM posts and holds all the post records.

Step 2 — It scans those records and collects every unique author_id value. If posts 1, 2, and 3 have author_ids of 7, 12, and 7, it collects [7, 12] (no duplicates).

Step 3 — It fires one query using SQL’s IN clause: SELECT * FROM users WHERE id IN (7, 12). This is equivalent to writing multiple WHERE id = ? conditions joined by OR, but in a single round-trip.

Step 4 — It matches each author back to their posts in PHP memory. No more database involvement.

Notice that even though posts 1 and 3 share the same author_id: 7, Laravel is smart enough to deduplicate — it only fetches that author once.

Nested relationships

with() handles deeply nested relationships using dot notation. If a post belongs to an author, and that author belongs to a company, you can load all three levels in one shot:

The number of queries is always equal to the number of distinct relationships being loaded, not the number of records. Loading three separate relationships for 1,000 posts costs exactly 4 queries (1 for posts + 3 for relationships).

Advanced patterns

1. Lazy eager loading

Already have a collection and realise you forgot a relationship? Use load() to eager-load it after the fact, without re-fetching the posts:

2. Conditional eager loading

You can apply constraints to the eager-loaded relationship — for example, only load published comments:

3. Counting without loading

If you only need the count of related records (not the records themselves), withCount() adds a _count attribute without pulling all the data into memory:

4. Enforcing eager loading globally

In Laravel 8+, you can prevent lazy loading entirely in your AppServiceProvider. This throws an exception any time your app tries to lazy-load a relationship in development, forcing you to catch N+1 problems before they reach production:

Use with care

preventLazyLoading() is aggressive — it will break any part of your application that relies on lazy loading. Enable it on a feature branch first and fix the errors one by one before committing to your main branch.

Detecting N+1 in your app

The best tools for finding N+1 problems in existing Laravel applications:

ToolHow it helpsBest for
Laravel DebugbarShows every query fired per request with timing, in a browser toolbarDevelopment — spot the 50 identical queries instantly
TelescopeLaravel’s official debugging dashboard; logs queries, jobs, mail, and moreLocal & staging environments
DB::listen()Hook into every query programmatically; log or count them yourselfCustom alerting, CI checks
preventLazyLoading()Throws an exception whenever lazy loading is triggeredEnforcement during development

The tell-tale sign in Debugbar: you’ll see dozens of identical queries with only the WHERE id = ? value changing. That pattern, repeated N times, is a confirmed N+1.

Summary & cheat sheet

The N+1 problem is one of the most common performance issues in Laravel, but also one of the easiest to fix. Here’s everything you need to remember:

Quick reference

The rule of thumb: If you’re looping over a collection and touching a relationship inside the loop, you almost certainly need with(). When in doubt, open Laravel Debugbar and count the queries. Identical queries firing in sequence is the smoking gun.