I remember staring at my screen, confused.
The page loaded. No errors flashed. Nothing broke. But something felt wrong.
Then I opened the Rails logs.
A waterfall of SQL queries cascaded down my terminal. One query triggered another. Then another. Then ten more.
My app was drowning in database calls, and I had no idea why.
That was the day I discovered the N+1 query problem. And if you're reading this, you're probably experiencing that same sinking feeling right now.
Don't worry. You're about to learn exactly how to fix it.
What N+1 Queries Really Mean for Your App
Let me show you the code that caused my headache:
@articles = Article.all
Simple, right? Then, in my view:
<% @articles.each do |article| %>
<%= article.author.name %>
<% end %>
Here's what Rails actually did behind the scenes:
1 query to fetch all articles
1 query per article to fetch each author
With 10 articles, that's 11 database queries. With 100 articles? 101 queries.
That's the N+1 problem in action. It works perfectly fine in development. But it absolutely destroys performance at scale.
Rails Actually Tells You Everything
Here's the beautiful truth: Rails never hides this from you.
When I finally learned to read my development logs properly, I saw patterns like this:
Admin Load SELECT "admins".* WHERE "admins"."id" = 1
Admin Load SELECT "admins".* WHERE "admins"."id" = 1
Admin Load SELECT "admins".* WHERE "admins"."id" = 1
The same query. Repeated over and over.
That repetition? That's Rails screaming at you.
Once you recognize this pattern, you'll spot N+1 problems instantly. It's like suddenly being able to see the Matrix.
How to Read Logs Like a Pro
Every SQL line in your logs is a clue.
When you see:
Category Load SELECT "categories".*
Rails is telling you: "Somewhere in your view, you called article.categories, but you didn't preload it."
The fix becomes obvious:
includes(:categories)
No guessing. No trial and error. Just reading what Rails is telling you.
If you're still getting comfortable with how Rails connects models, views, and controllers, understanding
Rails MVC architecture will make these patterns click even faster.
The ActionText Trap Everyone Falls Into
When you add rich text to your model:
has_rich_text :content
Rails creates a hidden association called rich_text_content.
So when your view renders:
<%= article.content %>
Rails fires off an N+1 query for ActionText::RichText.
I spent hours debugging this before I understood the pattern. The fix:
includes(:rich_text_content)
This catches almost every Rails developer at least once. Now you know the secret.
Active Storage Needs Two Includes, Not One
Here's another gotcha I learned the hard way.
When you attach an image:
has_one_attached :featured_image
Rails uses two separate tables internally:
- active_storage_attachments
- active_storage_blobs
If you only preload the attachment, Rails still queries for the blob later.
The correct solution:
includes(featured_image_attachment: :blob)
This applies to profile pictures, avatars, and any file attachment in your app.
Nested Includes: Following the Object Tree
What if your article displays the author's profile picture?
<%= image_tag article.author.profile_picture %>
Rails needs to preload:
- The article's author
- The author's profile picture attachment
- The attachment's blob
Which translates to:
includes(
author: { profile_picture_attachment: :blob }
)
It looks complex at first glance. But you're just following the chain of associations. Article to author. Author to picture. Picture to blob.
Simple logic, nested syntax.
What a Production-Ready Query Looks Like
After months of refining, here's what my controller query evolved into:
Article.includes(
:categories,
:rich_text_content,
:author,
author: { profile_picture_attachment: :blob },
featured_image_attachment: :blob
)
Every single line corresponds to something my view actually uses. Nothing extra. Nothing missing.
This query went from 50+ database calls down to 6.
The page load time dropped from 2 seconds to 200 milliseconds.
The Philosophy Behind the Fix
Rails is built on convention over configuration. Clarity over cleverness.
Rails wants you to be explicit about your intentions. When you use includes, you're telling Rails: "I know I'll need this data. Please fetch it upfront."
That's not a performance hack. It's just good design.
The Question That Changes Everything
Stop asking: "What should I put inside includes?"
Start asking: "What methods am I calling in my view?"
If your view calls it, Rails must preload it.
That single mental shift will guide you through every N+1 problem you'll ever face.
The Moment You Stop Feeling Like a Beginner
Every Rails developer reaches a turning point.
You stop blindly copying code from Stack Overflow or
AI assistants.
You start reading your logs.
You start understanding what Rails is actually doing beneath the surface.
That's when Rails stops feeling like magic and starts feeling like a tool you truly control.
I remember the first time I fixed an N+1 problem without googling anything. I just read the logs, spotted the pattern, and knew exactly what to add.
That moment felt incredible.
Because suddenly, performance wasn't scary anymore. It was predictable. It made sense.
And that's when I knew I was becoming a real Rails developer.
My advice for you: Keep building. Keep learning. Keep pushing forward.