Logics Guru
Performance

The N+1 Query Problem, and Four Ways to Catch It

Why the most common ORM performance bug hides in development and only appears under real data.

1 min read 4 views

An N+1 is one query to fetch a list, then one more per row to fetch a relation. With ten seeded rows it is invisible. With ten thousand it is an outage.

What it looks like#

PHP
$posts = Post::all();

foreach ($posts as $post) {
    echo $post->author->name;  // one query per post
}

The fix#

PHP
$posts = Post::query()->with('author')->get();

Catching it automatically#

Four approaches, in increasing order of usefulness:

  • Eyeballing the debug bar. Works, but only when someone looks.
  • Strict mode. Model::preventLazyLoading() in a non-production environment throws the moment a relation loads lazily.
  • Query-count assertions in tests. Assert that an index page issues a bounded number of queries. The test fails when someone adds a lazy relation later.
  • Production query logging with a threshold. Catches the ones that only appear with real data shapes.

Why strict mode alone is not enough#

It only fires on code paths you actually execute. A rarely visited page can still ship an N+1 for months. Pair it with query-count assertions on every list endpoint.

Mustasim Ali

Mustasim Ali

Senior Software Engineer & Technical Lead

Full-stack engineer working in PHP and Laravel since 2019. I lead a development team building web and mobile products, and spend most of my time in Laravel, Node.js, Vue and React against MySQL and MongoDB. Logics Guru is where I write up the things I had to work out the hard way — the architecture decisions, the debugging sessions, and the small utilities I kept rebuilding until I put them somewhere permanent. Everything here is what I actually use.

You might also like