🚨 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

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

🔹 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

🔹 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

🔹 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

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

💡 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

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

Leave a comment