Lesson 27 of 30
N+1 usually gets blamed on the database, but the frontend causes its own version of the exact same pattern — and it shows up as a very specific, recognizable burst of requests in the Network tab.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A page renders 30 comment components, and each one independently fetches its own author info on mount. What pattern is this, and where does it come from?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
N+1 is usually described as a backend problem: one query fetches a list of N rows, and then the code fires one additional query per row to fetch each row's related data — N+1 total queries where one join would have done the job. That's real, and it's a database-side concern.
But there's a frontend-caused version of the same shape, and it's just as common: a list renders with one request, and then each item's component independently fires its own follow-up request for that item's related data. Render a page of 20 posts, and if each PostCard component fetches its own author info on mount, that's 1 request for the list plus 20 more — one per post — fired independently by 20 separate components that have no idea the other 19 exist.
The fix isn't a database concept at all here — it's purely a frontend request-shaping decision: batch the follow-up requests into one, or push for a list response that already includes the related data inline.
// N+1: each PostCard independently fetches its own author
function PostCard({ post }: { post: Post }) {
const [author, setAuthor] = useState<Author | null>(null);
useEffect(() => {
fetchAuthor(post.authorId).then(setAuthor);
}, [post.authorId]);
return <div>{post.title} — by {author?.name ?? "..."}</div>;
}
function PostList({ posts }: { posts: Post[] }) {
return posts.map((post) => <PostCard key={post.id} post={post} />);
// 1 request for `posts`, then 20 more — one per PostCard
}
// Fixed: one batched follow-up request for every author the list needs
function PostList({ posts }: { posts: Post[] }) {
const authorIds = [...new Set(posts.map((p) => p.authorId))];
const [authors, setAuthors] = useState<Record<string, Author>>({});
useEffect(() => {
fetchAuthorsByIds(authorIds).then(setAuthors);
// 1 request for `posts`, 1 batched request for every author needed
}, [authorIds.join(",")]);
return posts.map((post) => (
<PostCard key={post.id} post={post} author={authors[post.authorId]} />
));
}
The batched version collects every author id the list actually needs, fires a single request asking for all of them at once, and hands each PostCard its author as a prop instead of letting the component fetch for itself. The other real fix — often the better one when you have any say over the API — is an endpoint that returns author data embedded in each post to begin with, so no follow-up request is needed at all (the same "don't force a follow-up request for data the client obviously needed" idea from the earlier lesson on shaping frontend-friendly API responses, applied to exactly this situation).
PostCard component's own fetch looks completely reasonable read in isolation — the N+1 burst only becomes visible when you look at what happens when 20 of that component render at once, the same blind spot that makes request waterfalls hard to spot from code alone."You render a list and each row fires its own request for related data — what's happening and how do you fix it?" is a real, practical question, and naming the N+1 shape, the Network-tab burst signature, and batching (or pushing for an inline API shape) as the fix demonstrates you've actually debugged this in a real app, not just heard the database version of the term.