Investigate JavaScript Thread Performance
Thread state visualization lets you visualize what states the threads are in during an app lifecycle. It can help you troubleshoot and optimize the performance of your JavaScript (JS) apps.
To use thread state visualization effectively, follow the guidance on this page.
Understand thread states
Threads can exist in various states, such as Running, Runnable, Runnable (Preempted), Sleeping, Uninterruptible Sleep, Stopped, Idle, and Exit. For exact definition of each state, see Thread States in Monitor and Record Your App Performance.
Identify causes of performance issues
Thread state visualization can provide insight into an apps performance issues, such as increased latency, unresponsiveness, crashes, and inconsistent behavior. The following table lists some of the possible causes of performance issues that can be identified using thread state information.
Cause | Description | Related thread states |
---|---|---|
Long-running threads | Threads that remain in a "Running" state for extended periods may indicate inefficient processing or long-running operations that could benefit from optimization. | Running |
High CPU usage | A situation where many threads are actively in a "Running" state, which can suggest inefficient algorithms or excessive context switching, leading to resource strain. | Running, Runnable |
CPU contention | High levels of contention occur when multiple threads compete for limited CPU resources. This can lead to threads being preempted or waiting for access to shared resources. | Runnable (Preempted) |
Use Vega Studio Performance Extension
Vega Studio Performance Extension (KSPE) provides thread state visualization in the Recording view of the Activity Monitor and Memory Monitor. To troubleshoot performance issues in your JS apps, complete the following steps:
- Start a recording in the Activity Monitor.
- Replicate the problematic behavior in the app.
- Stop the recording to generate the recording view, which includes the Traces view with thread state information.
Analyze thread state data
To identify app performance issues:
- Analyze the thread state data in Traces view.
- Examine if the thread state data corresponds to any of the previously mentioned causes: long-running threads, high CPU usage or CPU contention.
Use Cases
The following examples show how thread state visualization can help troubleshoot and optimize app performance.
Use case 1: Troubleshoot long-running threads due to inefficient algorithms
While testing VegaVideoApp, you experienced poor responsiveness issues when pulling up the Home Screen or any detail pages.
To identify the issue, you started a recording in the Activity Monitor while replicating the problematic behavior in the app. When the recording stopped, the Activity Monitor generated a recording view, including the Traces view with thread state information. You examined the Traces, which showed JS Thread was running for up to 5 seconds. This indicated long-running thread due to inefficient algorithms.

You examined the CPU Profiler Flamegraphs, where the DetailsScreen
was taking 5 seconds due to the getClassics
method.

While revisiting your app code, you noticed inefficient code while sorting and filtering the data, causing performance lag issue.
export const getClassics = (): TitleData[] => {
const classics = [
{
id: '169327',
title: 'Ethereal Echos',
description: 'Lorem ipsum dolor sit amet',
mediaType: 'video',
mediaSourceType: 'url',
duration: 300,
thumbnail: tile09,
posterUrl: tile09,
videoUrl:
'https://edge-vod-media.cdn01.net/encoded/0000169/0169313/video_1880k/T7J66Z106.mp4?source=firetv&channelID=13454',
categories: ['Hits'],
channelID: '13455',
rating: '5',
releaseDate: '09/20',
format: 'MP4',
uhd: false,
secure: false,
rentAmount: '200',
},
...
...
];
// Inefficient sorting
const sortClassics = (array: TitleData[]): TitleData[] => {
const n = array.length;
for (let i = 0; i < n; i++) {
for (let j = 0; j < n - i - 1; j++) {
for (let k = 0; k < n; k++) {
if (array[k].title === 'NA') {
continue;
}
}
if (array[j].title.localeCompare(array[j + 1].title) > 0) {
// Swap if the current element is greater than the next
const temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
return array;
};
// Inefficient filtering using nested loops
const filterClassics = (array: TitleData[], category: string): TitleData[] => {
const result: TitleData[] = [];
for (let i = 0; i < array.length; i++) {
let found = false;
for (let j = 0; j < array.length; j++) {
if (array[i].categories.includes(category) && array[i] === array[j]) {
found = true;
break;
}
}
}
return result;
};
const sortedClassics = sortClassics(classics);
const filteredClassics = filterClassics(sortedClassics, 'Hits');
return filteredClassics;
};
To resolve the issue, you can remove the unnecessary nested loops. Then, revisit the Traces view to validate if your app performance improves.

Use case 2: Troubleshoot high CPU usage in a scrolling app
You created a simple scrolling app using the React Native Flatlist component to display 200 rows with different colors. Using the Activity Monitor, you observed frequent "running" states in the thread state view during scrolling, indicating high CPU usage. The CPU Profiler Flamegraphs also showed high activity, suggesting the Flatlist component wasn't functioning efficiently.

The following example code shows how Flatlist component was added in the app.
const PerfFlatListApp = () => {
// https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem
const renderItem = useCallback(
({ item, index }: { item: FlatListItem; index: number }) => (
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-basic-components
<Item
key={item.title}
buttonStyle={[
styles.item,
{
backgroundColor: calculateBackgroundColor(index)
}
]}
hasTVPreferredFocus={item.id === 0}
/>
),
[]
);
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-keyextractor-or-key
const keyExtractor = useCallback(
(item: FlatListItem, index: number) => item.title,
[]
);
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-getitemlayout
const getItemLayout = useCallback(
(
_data: any,
index: number
): { length: number; offset: number; index: number } => ({
index,
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index
}),
[]
);
useEffect(() => {
console.log('reporting fully_drawn');
// @ts-ignore
global.performance.reportFullyDrawn();
}, []);
return (
<View style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
// https://reactnative.dev/docs/optimizing-flatlist-configuration#initialnumtorender
// explicitly setting it to 10 since that is the initial number of viewable items on screen
initialNumToRender={10}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
// https://reactnative.dev/docs/optimizing-flatlist-configuration#windowsize,
// setting a higher window size on MM than on TV profiles since a quick touch
// swipe will scroll the flatlist faster than holding down DPad. Larger window
// size will take more memory but will reduce blank ares seen when scrolling.
windowSize={Platform.isTV ? 5 : 13}
/>
</View>
);
};
To address the performance issues, replace the Flatlist component with Shopify’s Flashlist component, which is designed for improved performance in scrolling scenarios.
const PerfFlashListApp = () => {
// https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem
const renderItem = useCallback(
({ item, index }: { item: FlashListItem; index: number }) => (
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-basic-components
<Item
buttonStyle={[
styles.item,
{
backgroundColor: calculateBackgroundColor(index)
}
]}
hasTVPreferredFocus={item.id === 0}
/>
),
[]
);
// https://reactnative.dev/docs/optimizing-flatlist-configuration#use-keyextractor-or-key
const keyExtractor = useCallback(
(item: FlashListItem, index: number) => item.title,
[]
);
useEffect(() => {
console.log('reporting fully_drawn');
// @ts-ignore
global.performance.reportFullyDrawn();
}, []);
return (
<View style={styles.container}>
<FlashList
estimatedItemSize={ITEM_HEIGHT}
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
</View>
);
};
Retest you app's performance, and validate if you see a reduction in the number of "running" states in the thread state view. The CPU Profiler Flamegraphs should also show a decrease in activity.

To read about the advantages of Flashlist over Flatlist, see Best Practices.
Related topics
- App Performance Best Practices
- Measure App KPIs
- Identify UI Rendering Issues
- Detect Overdraw
- Investigate Component Re-rendering Issues
Last updated: Sep 30, 2025