📌Problem Statement
Design infinite scroll for a social media feed with thousands of posts.
📌Challenges
📌Basic Implementation
const InfiniteScroll = () => { const [items, setItems] = useState([]); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [isLoading, setIsLoading] = useState(false); const observerRef = useRef(); const loadMoreRef = useRef(); useEffect(() => { observerRef.current = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMore && !isLoading) { loadMore(); } }, { threshold: 0.1, rootMargin: '100px' } ); if (loadMoreRef.current) { observerRef.current.observe(loadMoreRef.current); } return () => observerRef.current?.disconnect(); }, [hasMore, isLoading]); const loadMore = async () => { setIsLoading(true); const newItems = await fetchItems(page); setItems(prev => [...prev, ...newItems]); setPage(prev => prev + 1); setHasMore(newItems.length === PAGE_SIZE); setIsLoading(false); }; return ( <div> {items.map(item => <FeedItem key={item.id} item={item} />)} <div ref={loadMoreRef}> {isLoading && <Spinner />} </div> </div> );};📌Virtual List Implementation
Only render visible items:
const VirtualList = ({ items, itemHeight, containerHeight }) => { const [scrollTop, setScrollTop] = useState(0); const startIndex = Math.floor(scrollTop / itemHeight); const endIndex = Math.min( startIndex + Math.ceil(containerHeight / itemHeight) + 1, items.length ); const visibleItems = items.slice(startIndex, endIndex); const totalHeight = items.length * itemHeight; const offsetY = startIndex * itemHeight; return ( <div style={{ height: containerHeight, overflow: 'auto' }} onScroll={(e) => setScrollTop(e.target.scrollTop)} > <div style={{ height: totalHeight, position: 'relative' }}> <div style={{ transform: `translateY(${offsetY}px)` }}> {visibleItems.map((item, index) => ( <div key={startIndex + index} style={{ height: itemHeight }}> {item.content} </div> ))} </div> </div> </div> );};📌Variable Height Items
For items with different heights:
const DynamicVirtualList = ({ items }) => { const [measurements, setMeasurements] = useState({}); const containerRef = useRef(); const measureItem = (index, height) => { setMeasurements(prev => ({ ...prev, [index]: height })); }; const getItemOffset = (index) => { let offset = 0; for (let i = 0; i < index; i++) { offset += measurements[i] || ESTIMATED_HEIGHT; } return offset; };};📌Scroll Position Restoration
const useScrollPosition = (key) => { useEffect(() => { const saved = sessionStorage.getItem(`scroll-${key}`); if (saved) { window.scrollTo(0, parseInt(saved)); } return () => { sessionStorage.setItem(`scroll-${key}`, window.scrollY); }; }, [key]);};📌Performance Tips
Asked at Meta, Twitter, and LinkedIn interviews.