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?