System Designhard11 min read

Design a News Feed

Infinite scroll done right: pagination strategies, optimistic updates, feed virtualization, image loading, and keeping a long-lived feed fast and accessible.

Published · by Frontend Masters India

A news feed is the classic "design Facebook/Twitter" frontend prompt. The trap is treating it as "a list with infinite scroll." The real difficulty is keeping a feed that grows to thousands of items fast, correct, and accessible over a long session.

1. Clarify scope

  • What's in a post? Text, images, video, reactions, comments? Each adds loading and layout concerns.
  • How fresh must it be? Real-time push, pull-to-refresh, or load-on-navigate?
  • Can users act on posts? Likes and comments mean optimistic updates and conflict handling.

2. Pagination: cursor, not offset

Offset pagination (?page=2) breaks on a feed because new items shift everything down, so you get duplicates and skips. Use cursor-based pagination: the server returns a cursor pointing at the last item, and you ask for "items after this cursor."

GET /api/feed?limit=10
→ { items: [...], nextCursor: "abc123" }

GET /api/feed?limit=10&cursor=abc123

3. Infinite scroll with IntersectionObserver

Watch a sentinel element near the bottom of the list; when it enters the viewport, fetch the next page.

const observer = new IntersectionObserver(([entry]) => {
  if (entry.isIntersecting && !loading) loadMore();
});
observer.observe(sentinelRef.current);

Always pair infinite scroll with a "Load more" fallback for keyboard users and offer a path to the footer. Pure infinite scroll is an accessibility and SEO trap.

4. The feed will get slow, so virtualize it

After scrolling a while, thousands of DOM nodes will tank performance. Windowing (react-window, TanStack Virtual) renders only the visible rows plus a small buffer. The catch: feed items have variable heights (an image post vs. a one-liner), so you need dynamic measurement, not fixed row heights.

5. Layout shift and images

Images that load after layout cause jarring cumulative layout shift. Reserve space with known aspect ratios (aspect-ratio or width/height attributes), lazy-load off-screen images (loading="lazy"), and show a low-quality or blurred placeholder first.

6. Optimistic updates for likes

When a user likes a post, update the UI immediately and reconcile with the server in the background. On failure, roll back and surface a quiet error.

setLiked(true);              // optimistic
try {
  await api.like(postId);
} catch {
  setLiked(false);           // rollback
  toast("Couldn't save your like");
}

7. Freshness without losing the user's place

New posts arriving shouldn't yank the scroll position. The standard pattern: buffer them and show a "5 new posts" pill at the top that the user taps to insert them. Never reflow under their thumb.

8. What the interviewer will push on

  • "How do you avoid duplicate posts across pages?" Cursor pagination + de-duping by ID on the client.
  • "What about a slow network?" Skeleton placeholders, retry with backoff, and cached first page for instant paint.
  • "How do you keep memory bounded over a long session?" Virtualization plus optionally dropping far-off-screen data from state.

The one-paragraph summary

A production feed uses cursor pagination, loads more via IntersectionObserver (with a keyboard-accessible fallback), virtualizes variable-height rows to stay fast, reserves space for media to avoid layout shift, applies optimistic updates with rollback for actions, and buffers new posts behind a pill so the user never loses their place.

Before you leave — how confident are you with this?

Your honest rating shapes when you'll see this again. No grades, no shame.

Comments

to join the discussion.

Loading comments…

More design prompts