You built a simple blog, but every time you load the homepage, your database logs light up with dozens of identical queries. This performance killer is known as the N+1 query problem, and it can bring a fast app to its knees as data grows.
Understanding this issue early will save you hours of debugging when your user base finally starts to grow.
- What an N+1 query actually is under the hood
- Why this happens when fetching related database records
- How to spot N+1 queries in your application logs
- How to fix the problem using eager loading
What is an N+1 Query?
An N+1 query happens when your code makes one database query to fetch a list of parent items, and then makes N additional queries to fetch the related children for each item. If you have 50 users, your app might make 1 query to get the users, and then 50 separate queries to get each user's profile.
Your database hates this because opening and closing network connections takes time. Doing it fifty times in a row makes your page load painfully slow.
Seeing the Problem in Code
Here is a classic example of lazy loading in a Node.js environment that triggers the N+1 problem. The code fetches all posts and then loops through them to fetch the author for each one.
const posts = await db.query('SELECT * FROM posts');
for (const post of posts) {
const author = await db.query('SELECT * FROM users WHERE id = ?', [post.author_id]);
console.log(post.title, author.name);
}The key takeaway is that the database runs a brand new query inside every single loop iteration.
How to Fix It With Eager Loading
To fix this, you need to fetch all the data you need in a single query using a technique called eager loading, often done with a SQL JOIN. Instead of asking for data one piece at a time, you grab everything in one trip to the database.
Here is how you fix the exact same problem by joining the tables together:
const postsWithAuthors = await db.query(
'SELECT posts.title, users.name FROM posts JOIN users ON posts.author_id = users.id'
);
for (const row of postsWithAuthors) {
console.log(row.title, row.name);
}The key takeaway is that your app now makes exactly one database trip, no matter how many posts you have.
Common mistakes
A common mistake is assuming your ORM (Object-Relational Mapper) protects you from this automatically. Many popular libraries use lazy loading by default, meaning they will quietly fire off extra queries behind the scenes until you explicitly tell them to eager load the relations.
Another mistake is ignoring database logs during local development. Always turn on query logging so you can spot loops that trigger unexpected database traffic.
Open up your current project's database logs today and check if a simple page load is triggering more queries than it should.