Skip to main content

Performance model

The index builds asynchronously, queries are synchronous, and rendering is the ceiling. Where each step runs decides what a large dataset costs.

What runs where​

StepWhere it runsWhen
Turning <Marker> children into points, and comparing them with the previous pointsJS threadEach render with new children
Packing the points into a buffer and copying them into the engine (setPoints)JS thread; the copy is a synchronous native callWhen markers are added, removed, moved or reordered
Building the indexA background threadAfter each copy; when a few markers moved and minPoints is 2 or less, only around them (see live updates)
Finding the clusters in the region (getClusters)JS thread, a synchronous native callEach region update; while the map moves, at most every clusterUpdateIntervalMs; after a cluster press, once the camera settles
Reading a cluster's markers (getLeaves)JS thread, a synchronous native callOn a cluster press with onClusterPress, and for spiderfy
Mounting and drawing markers and cluster bubblesreact-native-maps, on the UI threadWhenever the visible markers and clusters change
Fading and gliding the default bubblesReanimated, on the UI threadOn zoom

What it means​

  • Building. react-native-map-clustering builds its supercluster index on the JS thread. Here, turning the children into points, packing them and copying them into the engine run there, but the build does not. Those steps still grow with the number of markers.
  • Querying. A query blocks the JS thread for as long as it takes, and returns only the features in the region.
  • Rendering is the ceiling. Each visible marker and cluster is a native map annotation that react-native-maps mounts. With hundreds on screen, a zoom step that replaces them costs more than clustering. Keep the count down with a larger radius (see Zooming stutters with many markers), a smaller clusterRenderMargin, and stable keys on your markers.

Measurements​

The core benchmark measures the C++ engine alone on a desktop CPU: build and query times, and memory, for six datasets of up to 100k points. It says nothing about Hermes, the JS layer or rendering on a phone, so it cannot back a claim about app frame rates.

The device benchmark measures the whole pipeline instead: the example app in a Release build on Hermes, rendering included, running this library and react-native-map-clustering 4.0.0 through the same script. Its first run, on the iOS Simulator, reports the time to the first markers, JS freezes, memory, zoom and cluster presses for 10k, 50k and 100k markers. Since 29 October 2026, on the iOS Simulator and an Android emulator, it also moves 20 vehicles over the markers 10 times a second, and reports how many updates reach the map, how late and how old the map is, and how long JS is blocked meanwhile. A simulator runs on the Mac's CPU, faster than a phone: compare the libraries, not the absolute times. On a mid-range Android phone (Snapdragon 720G), with 50k markers the map stays on average 0.44 s old, against 2.8 s with react-native-map-clustering.

The hook benchmark​

The hook benchmark runs useClusterer, react-native-clusterer 5.0.2's useClusterer and use-supercluster 1.2.0 (on supercluster 8.0.1) in the same Release build, with the same points and options and no map: what clustering costs the JS thread, not rendering. Its first run, on the iOS Simulator, medians of 3:

PointsLibraryIndex build, on the JS threadIndex build, until it answers queriesEach re-render, same propsEach panEach data change, until the new index
10,000react-native-clusterer 5.0.219 ms19 ms21 ms43 ms63 ms
use-supercluster 1.2.0229 ms229 ms0.7 ms223 ms226 ms
This library1.2 ms9.5 ms0.7 ms0.7 ms14 ms
50,000react-native-clusterer 5.0.2104 ms104 ms111 ms216 ms321 ms
use-supercluster 1.2.01,126 ms1,126 ms0.7 ms1,205 ms1,202 ms
This library5 ms50 ms0.6 ms0.8 ms59 ms
100,000react-native-clusterer 5.0.2234 ms234 ms238 ms455 ms674 ms
use-supercluster 1.2.02,340 ms2,340 ms0.7 ms2,380 ms2,338 ms
This library11 ms125 ms0.7 ms0.7 ms152 ms
  • Building. react-native-clusterer reads the points and builds its index in one synchronous JSI call, and supercluster builds in JavaScript: the whole build blocks the JS thread. Here only checking and packing the points does.
  • Re-renders and pans. react-native-clusterer 5.0.2 builds a full index on every render of the component that calls its hook, so one for each re-render and two for each pan. use-supercluster builds one for each pan unless its options spell out every supercluster option, which the benchmark's don't (see Migrating from use-supercluster). Here a pan only queries the index. Times below a frame (16.7 ms) are mostly the wait for React to commit.
  • Where this library is behind. Its hook has no clusters on the first render: they arrive with the first build, 15 ms after the mount at 10,000 points and 165 ms at 100,000. react-native-clusterer has them on the first render, by building during it. While a new index builds, the hook keeps returning the clusters of the previous points: 152 ms at 100,000 here.
  • Feature counts. With the same options, useClusterer and react-native-clusterer returned the same number of features at every step.

Roadmap​

Today this library moves clustering compute to C++. Visible markers are still rendered through react-native-maps <Marker> components, which is the ceiling described above.

  • Near term, within react-native-maps: image-based cluster bubbles with caching, stable marker pooling and smarter defaults. Still bounded by the cost of each annotation.
  • The goal: a native render layer. The C++ engine feeds packed point data directly into native bubble rendering, drawn in a single native pass instead of one React <Marker> per cluster or point, behind the same MapView + <Marker> API.

Track progress in GitHub Issues.