Spoiler: It wasn’t my server specs.
So there I was, deploying my app — can’t drop the real name, NDA things 😅 — feeling pretty good about myself. Everything worked perfectly in development, right?
Wrong.
The moment real users started hitting my API, everything went to hell. Response times went from 200ms to 5+ seconds.
My database was screaming.
Users were complaining.
I was panicking.
“But it works fine on my machine!” 🤦♂️
Turns out, I was making the classic N+1 mistake. And honestly? I had no idea what that even meant until I spent 3 days debugging this mess.
What the Heck is N+1?
Picture this: You’re fetching 100 courses from your database.
Sounds simple, right?
What I was doing (the wrong way):
// ❌ This looks innocent but it's a performance killer
const courses = await Course.find({}); // 1 query
for (let course of courses) {
const creator = await User.findById(course.createdBy); // 100 more queries!
course.creator = creator;
}
Total queries: 101 😱
That’s 101 round trips to your database.
Each one takes time.
Multiply that by concurrent users, and boom – your server is toast.
Here’s what my server logs looked like:
Query 1: Find courses (200ms)
Query 2: Find user for course 1 (50ms)
Query 3: Find user for course 2 (50ms)
Query 4: Find user for course 3 (50ms)
... (you get the idea)
Total time: 5.2 seconds for what should have been a 200ms request.
What I should have done:
// ✅ One query with a join - much better!
const courses = await Course.find({})
.populate('createdBy', 'firstName lastName email'); // Just 1 query!
Total queries: 1 🎉
The 3 Fixes You Can Implement Today
1. Use Populate (Mongoose)
// Instead of this mess:
const courses = await Course.find({});
for (let course of courses) {
course.creator = await User.findById(course.createdBy);
}
// Do this:
const courses = await Course.find({})
.populate('createdBy', 'firstName lastName email');
2. Add Database Indexes
// In your model file:
courseSchema.index({ createdBy: 1 }); // Index foreign keys
courseSchema.index({ status: 1, createdAt: -1 }); // Compound indexes
3. Use Aggregation for Complex Queries
// When you need multiple joins:
const result = await Course.aggregate([
{
$lookup: {
from: 'users',
localField: 'createdBy',
foreignField: '_id',
as: 'creator'
}
}
]);
The Real Impact
After fixing this, my app went from handling 50 concurrent users to 500+.
Same server, same codebase, just better queries.
Performance improvement: 96% faster
User capacity: 10x increase
My sanity: Restored 😅
What’s Your Experience?
Have you run into the N+1 problem?
What was your “oh shit” moment when you realized your queries were the bottleneck?
Drop a comment below – I’d love to hear your war stories!
If you want to understand different loop patterns in JavaScript, check out my earlier post:
👉 Confused About JavaScript Loops? We Can Help.
And if you want a deeper dive into the N+1 issue, this article is a great read:
👉 What Is the N+1 Problem — JavaScript Edition (Accreditly)