Design a News Feed (Frontend)
Concepts: Virtualization (Windowing), Infinite Scroll, Intersection Observer API, Optimistic Updates, State Management, Real-Time Updates (WebSocket/SSE), Image Lazy Loading, Skeleton Screens
Requirements
Functional Requirements
| Requirement | Description |
|---|---|
| Display posts | Render posts with text, images, and videos |
| Infinite scroll | Load additional content as user scrolls |
| Real-time updates | Display new posts without page refresh |
| Interactions | Support like, comment, and share actions |
| Responsive design | Adapt layout to different screen sizes |
Non-Functional Requirements
| Requirement | Target |
|---|---|
| Initial load | < 2 seconds |
| Scroll performance | 60 FPS |
| Memory usage | < 200MB on mobile devices |
Component Architecture
State Management
State structure: The feed state maintains a normalized data structure with posts stored in a Map (keyed by ID for O(1) lookups), a postIds array maintaining display order, hasMore boolean for pagination, isLoading for loading states, a cursor for pagination, and a newPostsCount for unread post notifications.
Actions: The state handles fetching posts (start, success, error states), adding new posts from real-time updates, and updating individual posts for optimistic updates.
| State Property | Purpose |
|---|---|
| posts | Normalized post data for O(1) lookups |
| postIds | Maintains display order |
| cursor | Enables cursor-based pagination |
| newPostsCount | Tracks unread posts for notification |
Infinite Scroll Implementation
Intersection Observer triggers data fetching when the user scrolls near the bottom.
Implementation approach: Create a custom hook that maintains a ref to an IntersectionObserver. The hook returns a callback ref to attach to the last element in the list. When the last element enters the viewport and more content is available, trigger the loadMore function. Disconnect and recreate the observer when dependencies change to prevent stale closures.
| Configuration | Purpose |
|---|---|
| rootMargin | Trigger before element enters viewport |
| threshold | Percentage of element visibility required |
Virtual Scrolling
Virtual scrolling renders only visible items, reducing DOM node count.
Implementation approach: Track the scroll position and calculate which items are visible based on item height and viewport size. Maintain start and end indices for the visible range, adding buffer items above and below. The outer container maintains the full height (total items multiplied by item height) to preserve correct scrollbar behavior. Use CSS transforms to position the visible items correctly within the container. On scroll events, recalculate the visible range and update state.
| Approach | DOM Nodes | Memory Usage |
|---|---|---|
| Render all | O(n) | High |
| Virtual scrolling | O(viewport) | Constant |
Real-Time Updates
WebSocket connection receives new posts without polling.
Implementation approach: Establish a WebSocket connection when the component mounts. Listen for incoming messages, parse the JSON data, and dispatch an action to update state with the new post. Close the connection on component unmount to prevent memory leaks.
New post indicator: Render a clickable indicator showing the count of new posts when count is greater than zero. Clicking the indicator loads the new posts into the feed.
| Update Strategy | Description |
|---|---|
| Show indicator | Display count of new posts; user clicks to load |
| Auto-prepend | Insert new posts at top automatically |
| Hybrid | Auto-update if scrolled to top; show indicator otherwise |
Optimistic Updates
Optimistic updates provide immediate feedback before server confirmation.
Implementation approach: When the user performs an action (such as liking a post), immediately dispatch the state change to update the UI. Then make the API call asynchronously. If the API call succeeds, no further action is needed since the UI is already correct. If the API call fails, dispatch a reverting action to restore the previous state and display an error message to the user.
| Scenario | Behavior |
|---|---|
| Success | UI already updated; no additional action |
| Failure | Revert to previous state; display error |
Performance Considerations
| Area | Technique |
|---|---|
| Memory management | Unmount off-screen video players |
| Image loading | Lazy load with placeholder; use srcset for responsive images |
| Accessibility | Keyboard navigation; screen reader announcements |
| Offline support | Cache feed data in IndexedDB or localStorage |
| Error handling | Retry failed requests; display error states |
Design Trade-offs
| Decision | Options | Considerations |
|---|---|---|
| Pagination | Cursor vs offset | Cursor handles insertions/deletions correctly |
| State normalization | Nested vs flat | Flat structure enables efficient updates |
| Virtual scrolling library | react-window vs custom | Library handles edge cases; custom offers flexibility |
| Real-time transport | WebSocket vs SSE | WebSocket for bidirectional; SSE for server-push only |