Blogs / Machine Coding Rounds

Frontend Machine Coding: How to Build an Infinite Scroll List

A detailed guide to building an infinite scroll list for frontend machine coding rounds, covering scroll detection, pagination, performance, and real-world edge cases.

Mar 20, 202616 min readSevyDevy Team
Frontend Machine CodingInfinite ScrollReactJavaScriptPerformanceUI Engineering

Table of content

  1. 1. Why Infinite Scroll Is Asked in Interviews
  2. 2. What Problem Are We Solving?
  3. 3. Visualizing the User Experience
  4. 4. Core Requirements
  5. 5. Step 1: State Design
  6. 6. Step 2: Fetching Data with Pagination
  7. 7. Step 3: Detecting Scroll Position
  8. 8. Step 4: Attaching Scroll Listener
  9. 9. Step 5: Initial Load
  10. 10. Complete Example Component
  11. 11. How the Flow Works Internally
  12. 12. Better Approach: Intersection Observer
  13. 13. Common Mistakes
  14. 14. Performance Considerations
  15. 15. Real-World Example
  16. 16. Final Takeaway

Why Infinite Scroll Is Asked in Interviews

Infinite scroll is a very common frontend machine coding question because it tests multiple real-world skills at once. It involves handling user scroll events, managing API pagination, preventing redundant requests, handling loading states, and optimizing performance for large datasets.

Unlike simple UI questions, infinite scroll reveals whether a developer understands how the browser works, how to avoid unnecessary renders, and how to design scalable data fetching patterns.

What Problem Are We Solving?

Instead of loading thousands of items at once, which is slow and inefficient, infinite scroll loads data in chunks as the user scrolls. When the user reaches near the bottom of the list, the next batch of data is fetched automatically.

This pattern is widely used in social media feeds, product listings, search results, and content platforms.

Visualizing the User Experience

Imagine a user opening a feed that shows 10 items initially. As they scroll down and reach near the bottom, another 10 items are fetched and appended. The process continues seamlessly until there is no more data. The user never clicks a 'Load More' button, yet the list keeps growing naturally.

Core Requirements

  • Load initial data when component mounts
  • Detect when user reaches near bottom
  • Fetch next page of data
  • Append new data without replacing old data
  • Prevent duplicate API calls
  • Handle loading and end states

Step 1: State Design

A clean state structure is the foundation of a good solution. The component must track the list of items, current page, loading state, and whether more data exists.

const [items, setItems] = useState<any[]>([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);

This structure ensures that UI, API calls, and scroll logic remain predictable.

Step 2: Fetching Data with Pagination

The API should support pagination. Each request fetches a specific page or offset of data.

const fetchData = async () => {
  if (loading || !hasMore) return;

  setLoading(true);

  const res = await fetch(
    `https://api.example.com/items?page=${page}`
  );
  const data = await res.json();

  setItems((prev) => [...prev, ...data.items]);

  if (data.items.length === 0) {
    setHasMore(false);
  } else {
    setPage((prev) => prev + 1);
  }

  setLoading(false);
};

Notice how new data is appended instead of replacing the old list. This is essential for infinite scroll behavior.

Step 3: Detecting Scroll Position

The key part of infinite scroll is detecting when the user has reached near the bottom of the page.

const handleScroll = () => {
  const scrollTop = window.scrollY;
  const windowHeight = window.innerHeight;
  const fullHeight = document.body.scrollHeight;

  if (scrollTop + windowHeight >= fullHeight - 200) {
    fetchData();
  }
};

The buffer (e.g., 200px) ensures that data is fetched slightly before the user reaches the exact bottom, creating a smooth experience.

Step 4: Attaching Scroll Listener

useEffect(() => {
  window.addEventListener("scroll", handleScroll);

  return () => {
    window.removeEventListener("scroll", handleScroll);
  };
}, [loading, hasMore]);

Cleaning up the event listener is important to avoid memory leaks and unexpected behavior.

Step 5: Initial Load

useEffect(() => {
  fetchData();
}, []);

This ensures the first set of data is loaded when the component mounts.

Complete Example Component

import React, { useEffect, useState } from "react";

export default function InfiniteScroll() {
  const [items, setItems] = useState<any[]>([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  const fetchData = async () => {
    if (loading || !hasMore) return;

    setLoading(true);

    const res = await fetch(
      `https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`
    );
    const data = await res.json();

    setItems((prev) => [...prev, ...data]);

    if (data.length === 0) {
      setHasMore(false);
    } else {
      setPage((prev) => prev + 1);
    }

    setLoading(false);
  };

  const handleScroll = () => {
    if (
      window.innerHeight + window.scrollY >=
      document.body.offsetHeight - 200
    ) {
      fetchData();
    }
  };

  useEffect(() => {
    fetchData();
  }, []);

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () =>
      window.removeEventListener("scroll", handleScroll);
  }, [loading, hasMore]);

  return (
    <div>
      {items.map((item, index) => (
        <div key={index}>
          <h3>{item.title}</h3>
        </div>
      ))}

      {loading && <p>Loading...</p>}
      {!hasMore && <p>No more data</p>}
    </div>
  );
}

How the Flow Works Internally

Initially, the first page is loaded. As the user scrolls down, the scroll event continuously checks position. When the threshold is reached, the next page is fetched and appended. This loop continues until the API returns no more data.

Better Approach: Intersection Observer

Using scroll events can be inefficient because they fire frequently. A more modern approach is using the Intersection Observer API to detect when a sentinel element becomes visible.

const observer = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    fetchData();
  }
});

This approach is more performant and avoids unnecessary calculations.

Common Mistakes

  • Triggering multiple API calls due to missing loading guard
  • Replacing instead of appending data
  • Not handling end of data
  • Using heavy scroll listeners without optimization
  • Ignoring cleanup of event listeners

Performance Considerations

  • Debounce or throttle scroll events
  • Use Intersection Observer for better performance
  • Implement virtualization for large lists
  • Avoid unnecessary re-renders

Real-World Example

In an e-commerce application, products are loaded in batches of 20. As the user scrolls, more products load automatically. If the user reaches the end of available products, a 'No more products' message appears. This improves engagement compared to pagination because the user continues browsing without interruption.

Final Takeaway

Infinite scroll is not just about detecting scroll position. It is about designing a smooth data-loading experience that scales efficiently. A strong implementation handles pagination, avoids redundant calls, manages state cleanly, and optimizes performance.

In machine coding rounds, the best solutions are those that combine correctness with real-world usability and performance awareness.

Related blogs