What is an N+1 query?
An N+1 query is a performance problem where your code runs far more database queries than it needs to. It’s one of the most common bugs in web applications, and it’s easy to introduce without realizing it, especially when using an ORM.
Here’s the classic scenario: you fetch a list of posts from the database (that’s 1 query), then loop through them and fetch the author of each post one by one (that’s N more queries, one per post). If you have 100 posts, you’ve just made 101 queries when you could have made 2. At small scale it’s invisible. At production scale it brings your app to its knees.
The real fix is a SQL join. Instead of querying posts and then querying authors separately, you write a single query that joins the two tables and returns everything at once. One round trip to the database, no matter how many posts you have.
Most ORMs make this easy. In Rails you use includes or joins. In Django you use select_related or prefetch_related. The abstraction varies but they all generate the join for you under the hood.
The tricky part is that N+1 queries are silent. Your code looks clean. The feature works. You only notice when response times start climbing and you open the query logs. Most frameworks have tooling to catch them in development. Rails has the Bullet gem, Django has the Debug Toolbar. It’s worth keeping them enabled so N+1s don’t slip into production.
When I started using Rails, that was the first time I really ran into N+1 queries. Coming from places where we wrote our own SQL, it was surprising that a framework could produce them without you realizing it. But once you understand that ORMs can generate queries lazily behind the scenes, it’s pretty easy to stay ahead of. Most ORMs have built-in tools to help. In Rails, the Bullet gem flags them automatically, and the pattern of using includes becomes second nature quickly.