-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
181: Common Ruby Interview Questions on Database Optimization
I've sat through dozens of Ruby interviews, and there is one specific area where candidates usually stumble: database optimization. Most people know the terminology, but when I ask them to actually look at a log file and tell me why a page is slow, they freeze. So, let's stop talking about theory and actually break something together.
That familiar wall of SQL
Imagine we're building a small community forum. We have User models and Post models. I want to render a list of the ten most recent posts, and right next to each post, I want to show the username of the person who wrote it. Here is the "intuitive" way most people write this in a controller or a view:
@posts = Post.limit(10).order(created_at: :desc)
@posts.each do |post|
puts "#{post.title} - Written by: #{post.user.username}"
end
On my machine, with five posts, this feels instant. But I'm looking at the Rails server logs, and I see something alarming. I see one query to get the posts, and then ten individual queries to get the user for each post. It looks like a waterfall of SELECT "users".* FROM "users" WHERE "users"."id" = ?. This is the classic N+1 problem. The "1" is the query for the posts; the "N" is the number of users we have to fetch.
In an interview, the answer they want is "eager loading." Let's try includes. I'll tweak the query:
@posts = Post.includes(:user).limit(10).order(created_at: :desc)
Now, when I check the logs, the waterfall is gone. ActiveRecord does two queries: one for the posts, and one massive query using WHERE "users"."id" IN (...) to grab all necessary users at once. We just traded ten round-trips to the database for one. That's a massive win for latency.
Tuning the data stream
Now, let's say our User model is huge. Maybe we're storing bios, profile settings, and encrypted tokens in that table. When I use includes(:user), ActiveRecord does a SELECT *. I'm pulling back kilobytes of data for every user just to display a 15-character username. It's wasteful.
I often see candidates suggest select here, but select doesn't play nicely with includes in the way you'd expect. If I just want a list of names for a dropdown or a simple report, I'll try pluck instead. Let's see what happens if I just need the usernames of everyone who has posted:
# This loads full User objects into memory
usernames = User.joins(:posts).distinct.map(&:username)
# This does it all in the database
usernames = User.joins(:posts).distinct.pluck(:username)
The first version is a memory hog. It instantiates a Ruby object for every single user. The second version—pluck—skips the ActiveRecord object creation entirely and returns a simple array of strings. If you're in an interview and you mention that pluck avoids the overhead of object instantiation, you're already ahead of 90% of the room.
When the dataset bites back
Finally, let's talk about scale. Let's say I need to run a cleanup script that sends an email to every single user who has an inactive account. I might be tempted to do this:
User.where(active: false).each do |user|
UserMailer.inactive_notification(user).deliver_now
end
I tried this on a staging database with 100,000 users, and the process crashed with an NoMemoryError. Why? Because .each on an ActiveRecord relation attempts to load every single record into a Ruby array before it starts iterating. My RAM simply couldn't hold 100,000 User objects.
I need to process these in chunks. I'll swap .each for .find_each:
User.where(active: false).find_each(batch_size: 1000) do |user|
UserMailer.inactive_notification(user).deliver_now
end
Now, if I watch my memory monitor, it stays flat. ActiveRecord fetches 1,000 records, processes them, throws them away, and fetches the next 1,000. It's the difference between trying to swallow a whole pizza in one bite and actually taking slices. When an interviewer asks about "batch processing" or "handling large datasets," find_each is your best friend.
📋 Practical Task
Optimizing the Analytics Dashboard Export
You have been handed a legacy report generator that is timing out in production. The current code fetches all Order records, finds the associated Customer for each to get their email, and calculates a total. It's currently written as follows:
# Current slow implementation
def generate_report
orders = Order.all
orders.each do |order|
puts "#{order.customer.email}: #{order.total_amount}"
end
end
Your Task: Rewrite the generate_report method to implement the following optimizations:
- Solve the N+1 query problem so that customers are loaded efficiently.
- Ensure the application doesn't crash on memory if the
orderstable grows to 500,000 rows. - Modify the logic so that it only pulls the
emailandidfrom the customers table, rather than the entire customer record (usepluckorselectwhere appropriate).
Provide your optimized Ruby code and a brief explanation of why your changes prevent the NoMemoryError and the N+1 issue.
There are no comments for now.