Skip to content

Reduce allocations in VirtualizedList render and scroll path - #58593

Open
secitr wants to merge 1 commit into
react:mainfrom
secitr:fix/virtualizedlist-reduce-allocations
Open

secitr wants to merge 1 commit into
react:mainfrom
secitr:fix/virtualizedlist-reduce-allocations

Conversation

@secitr

@secitr secitr commented Sep 18, 2026

Copy link
Copy Markdown

PR Draft: VirtualizedList — reduce allocations in the render and scroll path

Branch: fix/virtualizedlist-reduce-allocations
Base: react/react-native main (c687a35)


Summary:

VirtualizedList/FlatList/SectionList re-run a small amount of
bookkeeping on every render and on every scroll event (60–120 Hz on
ProMotion displays). This PR removes four allocations from those hot
paths without changing any observable behavior:

  1. VirtualizedList.render no longer builds a Set for
    stickyHeaderIndices on every render
    when the prop is not provided
    (the common case). The Set is now only created when the prop is
    present; the two .has() lookups are null-guarded.
  2. ChildListCollection.forEach returns early when there are no nested
    child lists
    (the common case) instead of allocating a
    Map.values() iterator. This is called from _onScroll and the four
    other scroll callbacks on every scroll event.
  3. _orientation() caches its result and only rebuilds the object
    when the horizontal prop changes. I18nManager.isRTL is a
    module-load constant (only changes on app reload), so the cache is
    invalidated solely by the horizontal prop. The object is replaced,
    never mutated, which keeps ListMetricsAggregator's field-based
    invalidation correct.
  4. The default keyExtractor does a single typeof check instead of
    one per property lookup. It is called per item on every render and on
    every viewability scan.

Changelog:

[GENERAL][CHANGED] - Reduce allocations in the VirtualizedList render and scroll path (avoid per-render Set allocation for stickyHeaderIndices, per-scroll-event Map iterator for the empty nested-list collection, per-call orientation object allocation, and duplicate typeof checks in the default keyExtractor)

Test Plan:

  • yarn test packages/virtualized-lists → 9 suites, 192 passed, 69
    snapshots (previously 179 tests; +13 new):
    • VirtualizeUtils-test.js: keyExtractor truth table (key/id
      precedence, explicit null key, falsy keys, null/undefined/
      primitive/array items)
    • ChildListCollection-test.js (new): forEach over populated/empty
      collection, removal, forEachInCell/anyInCell
    • VirtualizedList-test.js: stickyHeaderIndices not forwarded when
      the prop is absent (with ListHeaderComponent), forwarded when
      provided; orientation cache identity + invalidation on
      horizontal change
  • yarn flow-check → 0 errors
  • yarn lint → 0 errors, 0 warnings
  • yarn format-check (changed files)

Micro-benchmark (Node v24, V8, 2M iterations, before vs after, same
machine; the real-world benefit is dominated by reduced GC pressure,
which is largest on low-end Android):

keyExtractor (per item)                  18.74 ms → 13.29 ms  (-29.1%)
ChildListCollection.forEach, empty       9.29 ms → 8.65 ms    (-6.9%)
_orientation (per call)                  17.12 ms → 13.77 ms  (-19.6%)
sticky Set build + 100x .has (per render) 531.38 ms → 47.73 ms (-91.0%)
Simulated scroll event, 100 cells        192.17 ms → 158.92 ms (-17.3%)

I could not run Fantom integration tests (FlatList-itest.js,
SectionList-itest.js) as they require a native build environment.


@meta-cla

meta-cla Bot commented Sep 18, 2026

Copy link
Copy Markdown

Hi @secitr!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant