🇮🇳
🇮🇳
Republic Day Special Offer!Get 20% OFF on all courses
Enroll Now
P
Prakalpana
📚Learn
Code Your Future
System Design⏱️ 18 min read📅 Dec 27

UI System Design: Infinite Scroll & Virtualization

PS
Priya SinghStaff Engineer at Meta
📑 Contents (7 sections)

📌Problem Statement

Design infinite scroll for a social media feed with thousands of posts.

📌Challenges

  • DOM nodes grow unboundedly
  • Memory leaks
  • Scroll position management
  • Loading states
  • Back navigation
  • 📌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

  • 1Use keys properly - stable, unique IDs
  • 2Memoize items - React.memo for list items
  • 3Lazy load images - loading="lazy"
  • 4Debounce scroll - Don't update state on every pixel
  • 5Use CSS contain - contain: layout style paint
  • Asked at Meta, Twitter, and LinkedIn interviews.

    PS

    Written by

    Priya Singh

    Staff Engineer at Meta

    🚀 Master System Design

    Join 500+ developers

    Explore Courses →
    Chat on WhatsApp