Interview Question
"How do you find and fix N+1 queries?"
One of the most-asked Rails interview questions, and one of the best at separating mid from senior. What the interviewer is checking, the mid-level answer, the senior answer, and the follow-ups they will reach for.
What the interviewer is actually checking
The N+1 question is a load-bearing question for Rails interviews because it tests four things at once. Mechanical understanding of how Active Record generates SQL. Operational instinct for spotting performance problems in code review. Tooling familiarity (the right gems, the right log signals). Depth on the underlying SQL behavior, not only the Rails surface.
A weak answer mentions includes and stops. A strong answer covers detection, the four loading methods, the planner behavior, and the follow-up issues (eager loading the wrong shape, exploding result sets, memory cost on large preloads).
The interviewer is looking for someone who would actually solve the problem on the team's codebase, not someone who memorised the answer in a prep guide.
The mid-level answer
A typical mid-level response looks like this:
"N+1 is when you have one query to fetch a list, then one more query per row to fetch an associated record. You fix it withincludes— likePost.includes(:author). That tells Rails to load the authors in advance so it does not fire a query per post."
This is correct as far as it goes. What it misses is everything that makes the question interesting: how you would detect the problem in the first place, what includes actually does, when it is the wrong tool, and what happens when the preload itself becomes a performance problem.
For a senior position, this answer signals "knows the textbook fix, not what to do when the textbook fix has issues."
The senior answer
A senior answer covers four points: detection, the four loading methods, choosing the right one for the access pattern, and the failure modes seniors actually hit.
Detection. "I look at the Rails log in development and watch for repeated similar SQL in a single request. Three signals: a sequence of SELECT * FROM authors WHERE id = ? with different ids; the bullet gem flagging unused or missing eager-loads; pg_stat_statements in production showing one query with very high calls count. In an unfamiliar codebase, I would add bullet, run through the slow pages once, and let it report what is missing."
The four loading methods. "Active Record has four. preload always does two queries: one for the parent, one for the children with WHERE parent_id IN (...). eager_load does one query with a LEFT OUTER JOIN. includes picks one of the two based on whether the WHERE clause references the associated table; it is preload by default and upgrades to eager_load when needed. joins does an INNER JOIN but does not load the associated records into memory; useful only for filtering."
Choosing. "If I am only filtering by the associated table, joins is right. If I need to read the association after the query, preload is usually safer because the two-query shape avoids JOIN-induced row multiplication on collections. eager_load is right when I need to filter or sort by the association and read it after. includes is fine for simple cases but I lean toward explicit preload or eager_load when correctness matters."
Failure modes. "Two come up. First, eager loading a has_many that explodes: a Post with 1000 comments preloaded into memory uses 1000× the memory of the single Post row. extract_associated or a separate scoped query is better when the per-record collection is large. Second, eager loading a polymorphic association without specifying the types loads every concrete class, which can be wrong; use includes(:owner).where(owner_type: ...) or explicit polymorphic preloading."
The follow-ups
Most interviewers will press on three areas after the headline answer.
"What does includes actually do under the hood?" This is testing whether you know about the runtime decision between preload and eager_load. The right answer names the heuristic: Active Record checks if the SQL references the joined table; if yes, it generates a JOIN; if no, two queries. Mention references as the explicit hint for when the heuristic gets it wrong.
"You eager-loaded but the query is still slow. What now?" This is testing whether you can read the actual SQL. The right answer: open EXPLAIN ANALYZE on the generated query. Look at row estimates vs actual. Consider whether the JOIN is producing exploded result sets that need to be reorganised into preload-style two queries.
"Tell me about counter caches." Sometimes the interviewer wants to test whether you know the alternative to repeatedly querying for post.comments.count. Counter caches store the count denormalised on the parent; belongs_to :post, counter_cache: true on the child. Cheap reads, slightly more complicated writes (covered in Hot Rows & Contention when counters become a contention point).
What signals what
Some phrases in candidate answers carry strong signal.
- "I would add
includes." Junior. Knows the textbook answer, not the trade-offs. - "I use bullet in development." Mid. Has shipped enough to have hit the problem and reached for tooling.
- "
includesis fine, but I prefer explicit preload because of the JOIN-multiplication issue." Senior. Has hit edge cases and developed a heuristic. - "I look at pg_stat_statements first because the production version of this problem is the one that matters." Senior+. Thinks in terms of where the problem is observable, not only where it is in code.
The pattern: depth comes from having hit the failure modes, not from having read about the textbook fix. A candidate who can name one specific N+1 they fixed in a previous job, with the symptom and the actual change, is more convincing than one who recites the loading methods perfectly.
The principle at play
The N+1 question is short but it covers a wide surface: Rails internals, SQL, profiling tools, production debugging, and the trade-offs of denormalised counters. A senior answer threads through all of them in 60 to 90 seconds.
The interview reframing: when you get this question, do not optimise for completeness. Optimise for showing one specific war story plus the heuristic you took from it. That signals "this person has shipped Rails apps and learned from production" more reliably than reciting four loading methods.