Avoiding the N+1 Query Trap in Laravel with withCount()

Avoiding the N+1 Query Trap in Laravel with withCount()
Photo by Marcellin Bric / Unsplash

When working with Laravel Eloquent, one of the most common performance pitfalls is the N+1 query problem—and it often happens without developers realizing it.

Imagine you're displaying a list of blog posts along with the number of comments each one has. You might think this is straightforward:

$posts = Post::all();
foreach ($posts as $post) {
    $post->comments_count = $post->comments()->count(); // 😬
}

Looks simple, right? But under the hood, Laravel will:

  • Execute 1 query to fetch all posts
  • Then 1 query per post to count comments

That’s N+1 queries total, where N = number of posts. If you have 100 posts, that’s 101 queries. This quickly gets out of hand and kills performance, especially at scale.


✅ The Better Way: withCount()

Laravel provides a built-in, elegant solution: withCount(). Here’s how you should do it:

$posts = Post::withCount('comments')->get();

This executes only 1 query, using SQL’s COUNT() via a LEFT JOIN, and Laravel appends the result as a comments_count attribute to each post.

Now you can access:

echo $post->comments_count;

No extra queries. No loops. Just fast, clean, efficient code.


🧠 Why withCount() is Better

  • Performance: Reduces your queries from N+1 to just 1.
  • Clean Code: Avoids manual looping and counting logic.
  • Database Optimization: The database does the counting in one optimized query.
  • Scalability: Works well even with hundreds or thousands of records.

🚀 Bonus: Multiple Counts and Aliases

You can count multiple relations in one go:

$posts = Post::withCount(['comments', 'likes']);

Or use aliases for clarity:

$posts = Post::withCount([
    'comments as comments_total',
    'likes as total_likes'
]);

Now you can access $post->comments_total and $post->total_likes with ease.


⚠️ Common Mistakes to Avoid

  • Using count() in a loop — never do this unless you're intentionally querying live for one record.
  • Forgetting to index foreign keys — if you're using withCount() on large tables, make sure your foreign keys are indexed.
  • Assuming with() and withCount() are the samewith() eager loads the entire relationship, while withCount() only brings in the count.

Finally

When building performant Laravel applications, always be on the lookout for query inefficiencies. Small changes like replacing count() in a loop with withCount() can lead to massive gains in response time and resource usage.

Code smart, scale clean.

Support Us