Interview replay
Full round replay — the N+1 problem
Timed verbal replay with pass/fail criteria per follow-up.
How to run this
The opener
“What is the N+1 problem, and how do you detect it?”
Budget: 45 seconds. Going long here is itself a fail signal.
Follow-ups
1. “Why won't code review catch it?”
Testing: Whether they see why detection has to be mechanical.
Scoring
Pass: Because the expensive line is a getter. order.getItems() looks like reading a field you already have, and nothing at the call site distinguishes an initialised collection from a lazy proxy about to hit the database.
Fail: Says people should read more carefully.
2. “How would you detect it, concretely?”
Testing: A specific mechanism, not a tool name.
Scoring
Pass: Count statements — Hibernate statistics or a counting proxy — and assert an exact number in a test on the critical endpoints. Logging finds one instance; the assertion stops the next one.
Fail: Says turn on show-sql and read the logs, with no test.
3. “Why assert an exact count rather than an upper bound?”
Testing: Whether they have written one.
Scoring
Pass: A bound of 'under 10' passes at 9 when it used to be 1, so the regression lands silently. An exact number fails the moment the shape changes, which is the point.
Fail: Has no view, or prefers a bound for convenience.
4. “Name the fixes.”
Testing: Breadth, and knowing they differ.
Scoring
Pass: Join fetch or @EntityGraph for one query, batch size as a global default turning 1+N into 1+N/batch, subselect for two queries regardless of N, or a projection when the screen does not need entities at all.
Fail: Only knows join fetch.
5. “Why not just make the association EAGER?”
Testing: The most common wrong fix.
Scoring
Pass: Eager is a property of the mapping, so it fires on every query anywhere returning that entity, including screens that never touch the association. It usually raises the total count. Keep mappings lazy and fetch explicitly per query.
Fail: Offers EAGER as a legitimate fix.
6. “When is a join fetch the wrong choice?”
Testing: The limit of the standard answer.
Scoring
Pass: With pagination — Hibernate cannot apply the limit in SQL without truncating collections, so it reads everything and pages in memory, logging HHH000104. And with two collections on the same parent, where the join becomes a cartesian product.
Fail: Believes join fetch is universally correct.
7. “So how do you page a collection fetch?”
Testing: The correct shape.
Scoring
Pass: Two queries. Page the ids with a plain query the database can limit, then fetch collections for that page of ids. A repository method returning Page<T> with a join fetch in it is always a bug.
Fail: Suggests filtering in memory, or does not know.
8. “One query that reads fifty thousand rows, or eleven that read twenty. Which is worse?”
Testing: Whether 'fewer queries' has become a reflex.
Scoring
Pass: Depends on what you are optimising, and the one query is usually worse — round trips are cheap compared with reading and materialising the whole table. Count rows as well as statements.
Fail: Picks the single query automatically.
9. “One setting on a legacy codebase. Which?”
Testing: Practical judgement.
Scoring
Pass: hibernate.default_batch_fetch_size. No code change, and it converts every unfixed N+1 in the application to 1 + N/batch, including the ones nobody has found.
Fail: Proposes rewriting every repository method.
10. “Does @Transactional fix it?”
Testing: A confusion worth surfacing.
Scoring
Pass: No — it keeps the session open, so the lazy loads succeed instead of throwing LazyInitializationException. The N+1 still happens; it just stops being an exception and becomes a performance problem, which is harder to notice.
Fail: Thinks it helps.
Score yourself
← Back to What is the N+1 problem, and how do you detect it?