ESC

Type to search the knowledge base.

Design a News Feed

Frontend system design for a social news feed — ranking surface, virtualization, caching, realtime, and tradeoffs.

advanced4 min read
  • system-design
  • feed
  • performance
  • caching
  • interview

Scope the problem

Frontend system design interviews die when candidates design ranking ML and Cassandra clusters for 40 minutes. Lock scope early.

In scope (frontend):

  • Home feed (following / For You — pick one primary)
  • Post card: author, text, media, social counts, actions
  • Composer entry (not full rich-text editor deep dive unless asked)
  • Pagination / infinite scroll
  • Basic realtime (new posts indicator)
  • Performance, caching, a11y, empty/error states

Out of scope unless asked:

  • Fan-out-on-write vs fan-out-on-read internals
  • Ranking model training
  • CDN POP topology
  • Ads auction

Scale assumptions (state out loud):

  • Mobile-heavy; variable network
  • Media-heavy posts; long sessions
  • Personalized feed; tens of posts per viewport session
  • Read-heavy with bursty writes (likes/comments)

Requirements & metrics

Type Examples
Functional View feed, like, open comments, create text post
Non-functional TTI/LCP for first posts, scroll smoothness (INP), offline soft-fail
Product Time to first meaningful post, scroll depth, error rate

Clarify: chronological vs ranked; logged-in only; multi-media support.

High-level UI architecture

┌──────────────────────────────────────────────────────────┐
│ App shell (auth, nav, feature flags, toasts)             │
├───────────────┬────────────────────┬─────────────────────┤
│ Feed route    │ Composer modal     │ Post detail /       │
│ list + cache  │ (lazy)             │ comments route      │
├───────────────┴────────────────────┴─────────────────────┤
│ Design system: Card, Media, Avatar, ActionBar            │
└──────────────────────────────────────────────────────────┘

Route-split composer and comments so the feed bundle stays lean.

Component tree

FeedPage
├── FeedHeader / filters (Following | For You)
├── ComposerEntry
├── NewPostsPill          // realtime “N new posts”
├── VirtualizedFeedList
│   └── PostCard
│       ├── PostHeader
│       ├── PostBody (text clamp + expand)
│       ├── PostMedia (image/video)
│       └── PostActions (like, comment, share)
└── FeedSentinel / Load more

Data model (client)

type PostId = string;

type Post = {
  id: PostId;
  author: { id: string; name: string; avatarUrl: string };
  createdAt: string; // ISO
  text: string;
  media?: { type: "image" | "video"; url: string; width: number; height: number }[];
  stats: { likes: number; comments: number; shares: number };
  viewer: { liked: boolean };
};

type FeedPage = {
  items: Post[];
  nextCursor: string | null;
};

API sketch:

  • GET /feed?cursor=&tab= → FeedPage
  • POST /posts/:id/like → { liked, likes }
  • GET /posts/:id/comments?cursor=
  • Optional: WebSocket / SSE feed:new_post

Prefer cursor pagination over offset (stable under inserts).

Rendering & list performance

Critical path

  1. Shell + auth
  2. First feed page (SSR/stream if product allows; else client fetch with skeletons)
  3. Images below the fold lazy; LCP image prioritized

Virtualization

Mount ~10–20 cards in the viewport window. Estimate heights or measure; media aspect-ratio boxes reserve space (CLS).

See machine-coding cousin: Infinite Scroll Feed.

Media

  • Responsive images (srcset), blurhash/LQIP placeholders
  • Video: poster + click-to-play (autoplay muted only if product requires; respect data saver)
  • Fixed aspect boxes from server width/height

State & caching

Layer What Strategy
Memory (React Query / SWR) Feed pages by tab + cursor Infinite query; stale-while-revalidate
Normalized store posts by id Likes update one entity, all views agree
HTTP cache Avatars, static assets Long cache + hash
Service Worker App shell Network-first for feed API
Local Draft composer localStorage / IndexedDB

Like interaction: optimistic toggle → rollback on failure. Dedupe in-flight like requests.

New posts while scrolling: don’t jump the list; show a pill “View N new posts” that prepends or refreshes from top.

Realtime

Approach When
Polling every 30–60s Simple; fine for many products
SSE Server push, one-way
WebSocket Bidirectional; chat-adjacent products

Frontend concern: reconnect backoff, auth refresh, avoid stampeding re-fetch of full feed on every event (send ids, hydrate).

Accessibility

  • Feed as list of articles; action buttons named (“Like post by Ada”)
  • Keyboard operable actions
  • Infinite scroll + Load more fallback
  • Live region for “N new posts” (polite)
  • Reduced motion on media carousels

Performance budgets (example)

  • Feed route JS: < 150–200KB gzip initial
  • First posts painted with skeletons ≤ 1–2s on mid-tier 4G target
  • Scroll: avoid layout thrash in action bars; use CSS for like micro-interactions

Techniques: code-split comments, defer analytics, content-visibility for offscreen cards, virtualize early if demoing scale.

Tradeoffs to discuss

  1. SSR feed vs SPA — personalization vs first paint / cache
  2. Offset vs cursor — cursor wins for live feeds
  3. Optimistic likes vs wait for server — UX vs integrity
  4. Virtualization complexity vs max list length
  5. Realtime density — battery vs freshness

Closing structure

End with: scoped requirements → architecture → data/API → critical path → cache → realtime → a11y/perf → tradeoffs → monitoring (RUM: LCP feed, error rate, action latency).

Further reading