skip to content

In React Native, when is a ScrollView the right container, and when should a screen switch to a FlatList instead?

level: juniorimportance: must knowfreq 82%

answer

  1. everything mounts on first render
  2. native views exist for off-screen content
  3. bounded, known content is fine
  4. data-driven length is the trigger
  5. FlatList mounts rows near the viewport

basics

~20 s

A React Native ScrollView renders all its children at once, so it suits short, bounded content like a terms page or a settings form. Long or data-driven lists belong in a FlatList, which renders rows lazily and drops far ones.

solid answer

~40 s

`ScrollView` is a plain scrolling container: every child is created as a React component **and** a native view on the first render, whether or not it is visible. That is exactly right for content whose size you know and that fits in a few screens — a terms-of-service page, a settings screen with twenty heterogeneous rows, a form. When the length comes from data and can grow — a feed, search results, a message history — the up-front cost grows with it: slower first render, more memory, more views to keep. That is the point to switch to `FlatList`, which renders items lazily as they approach the viewport and removes items that scroll far away. `React.memo` does not fix a huge `ScrollView`, because the cost is the number of mounted children, not re-renders.

code

tsx · 23 lines
tsx
import {ScrollView, StyleSheet, Text, View} from 'react-native';

type Section = {id: string; title: string; body: string};

// Bounded, hand-authored content: a ScrollView is the simplest correct choice.
export function TermsScreen({sections}: {sections: Section[]}) {
  return (
    <ScrollView contentContainerStyle={styles.content}>
      {sections.map(section => (
        <View key={section.id} style={styles.section}>
          <Text style={styles.heading}>{section.title}</Text>
          <Text>{section.body}</Text>
        </View>
      ))}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  content: {padding: 16},
  section: {marginBottom: 24},
  heading: {fontSize: 18, fontWeight: '600', marginBottom: 8},
});

go deeper

for a junior

Remember the one rule: a ScrollView mounts all of its children, a FlatList mounts only those near the screen. Short, fixed content goes in a ScrollView; data-driven lists go in a FlatList.

for a middle

Explain where the cost lands: component renders and native views created up front, memory held for off-screen content, while the scroll gesture itself stays native. Say why React.memo does not change that.

for a senior

Show judgment about data growth: a list that is small in development and unbounded in production is a FlatList from the start. Diagnose a slow screen by measuring mount time and memory before touching the scroll code.

for a principal

Frame it as a default for the codebase: static screens stay simple ScrollViews, and any collection fed by data uses a virtualized list, reviewed as a rule rather than rediscovered per screen.

## What a ScrollView actually does `ScrollView` is React Native's generic scrolling container. It wraps the platform's native scroll view and places all of its children inside one **content container** view whose size defines how far the user can scroll. Its defining property is simple: **it renders every child at once**. When the screen mounts, React creates a component for each child and React Native creates the matching native view for each one, including content that is several screens below the fold and may never be seen. The official docs put it directly: a `ScrollView` "renders all its react child components at once", and creating JS components and native views for content that is not shown "will contribute to slow rendering and increased memory usage". The scrolling itself is native. The docs' performance guide notes that a `ScrollView` keeps scrolling while the JavaScript thread is busy, because the scroll view lives on the main thread; the JS side only receives scroll events. So a heavy `ScrollView` usually hurts **mount time and memory**, not the scroll gesture itself. ## The cost of mounting everything With a few dozen children, mounting all of them is cheap and gives you the simplest possible code. The cost scales with the child count: - **First render time** grows with every child, because every component renders and every native view is created before the screen is complete. - **Memory** holds every native view, including images and text far off screen. - **Updates** that change the collection (a new item, a filter) reconcile the whole set of children. - **`React.memo` does not help much**: it skips re-renders, but each child is still mounted once and stays mounted. ## When ScrollView is the right choice Use a `ScrollView` when the content is **bounded and known**: - a terms-of-service or privacy page made of a handful of long `Text` blocks; - a settings screen with a fixed set of rows, toggles and links; - a form, a profile page or an onboarding step whose sections are written by hand; - a short horizontal row of chips or cards, using `horizontal`. These screens are usually **heterogeneous** — each section looks different — which a `ScrollView` handles with no extra ceremony: you write the children in JSX. ## When to switch to FlatList | Question | ScrollView | FlatList | |---|---|---| | Where does the length come from? | written in code, bounded | data, possibly unbounded | | What mounts on first render? | every child | an initial batch near the viewport | | What happens to far off-screen items? | stay mounted | removed and re-created on demand | | How are items described? | JSX children | `data` plus `renderItem` | | Extras | none | separators, headers, columns, end-reached loading | The trigger is not a magic number of rows. It is the combination of **length that depends on data** and **a cost per row** you can feel: images, nested views, or anything that makes the first render visibly slow. A settings screen with 25 rows stays a `ScrollView`; the list of 25 notifications that can become 2,000 next month is a `FlatList` from day one. ## A quick decision checklist 1. Is the number of children fixed by the code, or does it come from an API, a database or user input? 2. Could that number reach hundreds on a real account? 3. Does each item carry images or deep view trees? 4. Do you need end-of-list loading, separators or multiple columns? If the answer to 1 is "data" and any of 2-4 is "yes", use `FlatList` (or `SectionList` for grouped data). Otherwise a `ScrollView` is simpler and perfectly fast. ## Common mistakes - **Mapping a server array into a `ScrollView`** because it worked with ten test records. It degrades silently as real accounts grow. - **Nesting a `FlatList` inside a vertical `ScrollView`** to get a header above it. React Native logs an error that this can break windowing; put the header in the list instead. - **Reaching for a list for everything**. A static form rewritten as a `FlatList` gains nothing and becomes harder to read. - **Blaming the scroll gesture**. When a big `ScrollView` feels slow, the problem is usually the time to mount it and the memory it holds, which only virtualization reduces.

  • Does wrapping each child of a 2,000-row ScrollView in React.memo make it fast?
    No. `React.memo` only skips re-rendering a child whose props did not change. Every child is still mounted on the first render, every native view is still created, and all of them stay in memory. The expensive part of a huge `ScrollView` is the number of mounted children, and only a virtualized list such as `FlatList` reduces that.
  • Is a settings screen with about 25 rows a ScrollView or a FlatList?
    A `ScrollView`, as long as the rows are fixed by the code. The content is bounded, the rows are heterogeneous (toggles, links, a version label) and the whole screen is cheap to mount, so a list adds ceremony for no gain. It becomes a list only when the rows come from data that can grow, such as a server-driven list of connected devices.
  • Does removeClippedSubviews turn a ScrollView into a virtualized list?
    No. It detaches clipped off-screen child views from their native superview, which can help scrolling on long content, and the docs warn it may cause missing content. The React components stay mounted and their native views still exist, so first-render time and most of the memory cost remain.

A ScrollView is a printed brochure: every page is printed before the reader opens it, which is fine for eight pages and absurd for a phone book. A FlatList is a counter clerk who fetches only the pages you are looking at and puts back the ones you have moved past.

saying these in an interview costs you the question

  • A ScrollView only renders the children that are currently visible.
  • A ScrollView stops scrolling whenever the JavaScript thread is busy.
  • Wrapping every ScrollView child in React.memo makes a huge ScrollView cheap.
  • FlatList is always better, even for a short static form.
  • There is a fixed row count, such as 50, above which ScrollView breaks.