DEV Community

Germán Alberto Gimenez Silva
Germán Alberto Gimenez Silva

Posted on • Originally published at rubystacknews.com on

🚨 Erratum: The N+1 Problem in Rails (and How I Redeemed Myself)

June 19, 2025

Yesterday morning, I had a deep and enjoyable technical interview covering a wide range of topics. But there was one moment I can’t forget: I completely fumbled a question about avoiding the N+1 problem in Rails.

Maybe it was the lack of coffee ☕ — or just nerves — but my brain didn’t cooperate, and ironically, the example I gave was the N+1 problem itself. 😅

So I did what any good developer should: I spent the afternoon (and part of this morning) revisiting my trusted Rails books and docs to refresh my knowledge. Here’s a breakdown of the problem — and the right ways to handle it.


❌ The N+1 Anti-pattern


User.all.each do |user|
  user.articles.each do |article|
    puts article.title
  end
end

Enter fullscreen mode Exit fullscreen mode

What happens here?

  • 1 query to get all users.
  • 1 additional query per user to get their articles.

That’s N+1 queries , and it kills performance as data grows.


✅ The Right Solutions

Article content

1. includes — Eager Loading


User.includes(:articles).each do |user|
  user.articles.each do |article|
    puts article.title
  end
end

Enter fullscreen mode Exit fullscreen mode

🔹 Rails runs 2 queries only : One for users, and one for all articles using WHERE user_id IN (…).

Use it when you’re displaying associated data without filtering or sorting it.


2. preload — Always Separate Queries


User.preload(:articles).each do |user|
  user.articles.each do |article|
    puts article.title
  end
end

Enter fullscreen mode Exit fullscreen mode

🔹 Forces Rails to run separate queries for each association (like includes, but never uses JOINs ). Good when you’re not using conditions or order clauses on the associated table.


3. eager_load — SQL JOIN


User.eager_load(:articles).each do |user|
  user.articles.each do |article|
    puts article.title
  end
end

Enter fullscreen mode Exit fullscreen mode

🔹 Uses a LEFT OUTER JOIN to load everything in one query. Best when you need to filter or sort based on attributes in the joined table (e.g., articles.published_at DESC).


4. Optimizing with pluck or select

If you only need a few fields:


User.includes(:articles).each do |user|
  user.articles.pluck(:id, :title)
end

Enter fullscreen mode Exit fullscreen mode

Or more advanced optimization with joins and select:


User.joins(:articles).select('users.*, articles.title AS article_title').each do |user|
  puts user.article_title
end

Enter fullscreen mode Exit fullscreen mode

💡 Bonus: Detect N+1 Issues Automatically

Use the bullet gem in development:


# Gemfile
gem 'bullet'

# config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.console = true
end

Enter fullscreen mode Exit fullscreen mode

It will warn you in real-time when you’re making N+1 mistakes.


🧠 Final Thoughts

We all have off days in interviews — but what really matters is how we respond. For me, this was a chance to sharpen my Rails skills and make sure I never miss this question again.

If you’re working with Active Record and associations, knowing how to avoid N+1 is essential. Bookmark this for the next time you’re optimizing your queries — or prepping for your own technical interview!

Article content

Top comments (0)