Skip to content
Frontend system design ยท about 45 minutes

News feed with infinite scroll

Design the home feed of a social product. The feed shows posts from people the user follows. A post has text, optional images, an author, a like count and a comment count. The user scrolls without an end, can like a post, and can write a new post. New posts arrive while the feed is open. Most users are on phones, and many have a slow network.

Ask first

  • Is the order by time, or does the server rank the posts? Can the order change between requests?
  • What can a post contain: text only, images, video, links?
  • Do new posts appear at once, or behind a button such as 'Show 5 new posts'?
  • Must like counts be live for everyone, or only correct after my own action?
  • Do we need server rendering for the first load, for speed or for search engines?
  • Must the feed keep its position when the user opens a post and comes back?

The outline

Write your own outline for each part first, then open it to compare with the points a strong answer covers.

Requirements
  • Functional: load the first posts, load more on scroll, like and unlike, create a post, see new posts
  • Fast first content on a slow phone: text is readable before images finish loading
  • Scrolling stays smooth after hundreds of posts
  • A like responds at once, also on a slow network
  • New posts never move the post that the user is reading
  • Keyboard and screen reader users can move from post to post and reach each post's controls
Architecture
  • Views: feed page, post list, post card, composer; a client store or query cache sits between the views and the API
  • The server builds, ranks and pages the feed; the client shows the order it receives
  • Pages load with a cursor; an IntersectionObserver on a sentinel near the end asks for the next page
  • New posts: polling every 30 to 60 seconds, or Server-Sent Events; they wait in a buffer until the user asks to see them
  • The first page can be rendered on the server and hydrated; later pages load on the client
  • A like is an optimistic mutation: it writes to the store first, then to the server
Data model
  • Post: id, authorId, text, media (url, width, height, alt), createdAt, likeCount, commentCount, likedByMe
  • User: id, name, avatarUrl; a post points to its author by id, so each user is stored once
  • Normalized store: postsById and usersById, and the feed as an ordered list of post ids
  • Feed state: nextCursor, hasMore, a status for the first load and one for the next page, and the ids of waiting new posts
  • A post I create has a temporary client id until the server returns the real id
Interface
  • GET /feed?cursor=abc&limit=20 returns { items: Post[], nextCursor: string | null }
  • PUT /posts/:id/like and DELETE /posts/:id/like return likeCount and likedByMe; both are idempotent, so a retry is safe
  • POST /posts with { text, mediaIds } and an idempotency key returns the created post
  • New posts: GET /feed/new?after=<newest id> returns them, or an event stream sends { type: 'post.created', post }
  • PostCard props: post, author, onLike, onOpen; the card does not fetch data itself
  • Media carries width and height, so the card can reserve space before the image loads
Optimizations
  • Virtualize the list: only posts near the viewport are in the DOM, and the total height is kept, so long sessions stay fast
  • Images: lazy loading, a size that fits the device (srcset), and reserved space to prevent layout shift
  • Optimistic like: the count and the icon change at once; on failure they go back and the user is told
  • New posts wait behind a 'New posts' button, so the reading position does not jump
  • Going back from a post restores the loaded pages and the scroll position from the cache
  • Accessibility: role feed with article items, aria-busy while a page loads, and a Load more button as a fallback
  • Errors: a failed next page shows a retry row at the end, and the loaded posts stay

Pitfalls

  • Offset pagination on a feed that changes, so the user sees duplicates or misses posts
  • Keeping thousands of post elements in the DOM
  • Adding new posts at the top while the user reads, so the content jumps
  • A scroll event handler that does work on every scroll event, where IntersectionObserver is enough
  • Images with no reserved space, which causes layout shift

Follow-ups an interviewer asks

  • Why a cursor and not a page number for this feed?
  • The like request fails after the icon changed. What happens next?
  • The user has scrolled past 2,000 posts. How do you keep memory and the DOM small?
  • Would you use a WebSocket for new posts? Why, or why not?
  • Post text contains links, mentions and line breaks. How do you render it safely?
  • The same post is on the feed and on a profile page. How do both show the new like count?