How I Evolved My React Native Performance Debugging — and How I Measure Performance Optimizations…
This is one of the approaches I use to debug performance in React Native, especially when performance issues become difficult to reason…
How I Took My React Native Performance optimization to next level with AI assistance
This is one of the approaches I use to debug performance in React Native, especially when performance issues become difficult to reason about as the app grows and saved me alot of time analyzing profiling data.
The Usual Way: Manual React DevTools Profiling
Like most developers, my performance profiling debugging used to follow the common flow:
- Open React DevTools
- Enable the Profiler
- Reproduce the laggy or slow scenario
- Stop profiling
- Manually inspect timelines, yellow bars, and component trees
- Try to reason about why certain components re-render
This approach works — but it doesn’t scale well.
As apps grow:
- Profiling sessions become large
- A lot of time goes into analyzing the data
- Prioritization becomes unclear
The problem isn’t lack of data — it’s too much context.
Export Profiler Data in a huge json file
When a profiling session ends, React DevTools allows you to export the results as a profiling.json file.

Press Save profile to export a .json file

the file itself
This file is the raw output of the profiling session — and in real production scenarios, it can easily contain up to 900,000+ lines of JSON.
At this scale:
- The file is impossible to reason about manually
- Feeding it directly to AI is inefficient and error-prone
- Important performance signals are buried under massive amounts of noise
This is the exact problem the script is designed to solve.
The Core Idea: Minimize Context, Maximize Signal
Instead of manually inspecting everything (or feeding massive profiler data to AI), I changed the goal entirely: I wrote a python script to
Reduce profiling data to the smallest possible context that still captures the real performance problem.
This is where a lightweight analysis script comes in.
Let a Script Structure the Data
The purpose of the script is not to optimize performance.
Its purpose is to:
- Filter out noise
- Aggregate repeated patterns
- Rank bottlenecks by impact
- Produce a compact, high-signal summary
In other words:
The script exists to minimize the required context for AI.
Instead of giving AI a profiling.json file with 200k+ lines of raw profiler output, AI only sees:
- Slow / very slow / critical renders
- Most expensive components (total + max render time)
- Re-render hotspots and frequent updaters
- Clear prioritization (ranked bottlenecks by impact)
But how do we make this script? I gave AI a sample of the porifling json to understand the structure of how it’s generated and let it make a script based on it

The script file

Categorized renderes
All i need to do is run :
python3 ./analyze_performance.py <exported-profiling.json>

Output of script in terminal
So once i run the script it outputs these results in terminal and I provide it to AI so it starts analyzing components once by one in their context
The AI-Assisted Workflow
- Profile the app using React DevTools
- Export the profiling session output as
profiling.json - Run the analysis script on that exported file
- Generate a structured performance report
- Feed only the report output to AI
- Apply targeted optimizations
AI never touches raw profiler data — it only reasons about structured insight.
What the Script Extracts (High Signal Summary)
The script focuses on extracting the information that actually helps with decisions:
- Categorizes renders by severity (example thresholds):
- Slow (≥16ms): dropped frames at 60fps
- Very slow (≥50ms): noticeable lag
- Critical (≥100ms): UI-blocking renders
- Aggregates per-component metrics:
- render count
- total render time
- average render time
- max render time
- Highlights
- worst commits (by duration)
- most expensive components (by total time)
- frequent updaters (high re-render count)
- slowest individual component renders
This transforms raw profiler output into decision-ready information.
Measuring Optimization by numbers (Before vs After)
Because the script output is structured and consistent, optimizations become measurable.
I run the script:
- Before optimization
- After optimization
Then compare both profiling json files:
- average commit duration
- number of slow / very slow / critical renders
- total render cost per component
- percentage improvement across the same scenario
This turns performance work from subjective into objective.
Example: Before vs After Results

Why This Matters
This approach allows me to:
- Verify that optimizations actually worked
- Avoid placebo performance fixes
- Communicate improvements clearly to: (Tech leads - Product managers - Stakeholders)
- Track performance regressions over time
It turns performance work into something that is measurable, repeatable, and defensible.
Note on Scope and Limitations:
This AI-assisted, profiler-driven approach is not a silver bullet, and it is not the only method you can use to debug performance issues.
What this method excels at is covering a large percentage of real-world performance problems we commonly face in React Native apps, such as:
- Unnecessary re-renders
- Over-updating parent components
- Expensive component trees
- Missing memoization (
React.memo,useMemo,useCallback) - Poor list rendering or virtualization
- Heavy components rendering too frequently
These are issues that often require well-known best practices and repeatable optimization techniques, and this approach helps surface them quickly, clearly, and at scale.
However, there can be deeper performance problems that this method alone will not fully solve, such as:
- Inefficient algorithms
- Poor time complexity (e.g. O(n²) work during renders)
- Expensive synchronous computations on the JS thread
- Heavy data transformations inside render cycles
- Native-side bottlenecks or bridge/JSI overhead
- Large JSON parsing or serialization costs
That said, even in these cases, this approach is still valuable.
While it may not directly fix such issues, it often points very clearly to where the lag originates, allowing deeper investigation to start in the right place instead of guessing.
Conclusion
React DevTools already provides all the raw data needed to understand performance issues in React Native applications. The real challenge is not collecting data — it’s making sense of it at scale.
By introducing a lightweight analysis script as a compression layer, and using AI only after the data has been structured, performance debugging becomes far more effective. Massive profiler outputs are reduced to high-signal insights, optimizations can be validated with real numbers, and decisions are based on evidence instead of intuition.
Once performance improvements are measurable, optimization stops being guesswork and starts being engineering.
Python script i generated to categorize the profiling.json file results
#!/usr/bin/env python3
"""
React Native Performance Profiler Analyzer
Analyzes React DevTools profiling data and extracts critical performance issues
"""
import json
import sys
from collections import defaultdict
from typing import Dict, List, Tuple
# Thresholds for what we consider "critical" (in milliseconds)
SLOW_RENDER_THRESHOLD = 16 # 16ms = one frame at 60fps
VERY_SLOW_RENDER_THRESHOLD = 50 # Very slow render
CRITICAL_RENDER_THRESHOLD = 100 # Critical performance issue
def load_profile_data(filepath: str) -> dict:
"""Load the profiling JSON data"""
print(f"📊 Loading profiling data from {filepath}...")
with open(filepath, 'r') as f:
return json.load(f)
def analyze_commits(data: dict) -> Dict:
"""Analyze commit data for slow renders"""
results = {
'total_commits': 0,
'slow_commits': [],
'very_slow_commits': [],
'critical_commits': [],
'component_stats': defaultdict(lambda: {'count': 0, 'total_duration': 0, 'max_duration': 0})
}
for root in data.get('dataForRoots', []):
for commit in root.get('commitData', []):
duration = commit.get('duration', 0)
results['total_commits'] += 1
# Categorize by severity
commit_info = {
'duration': duration,
'timestamp': commit.get('timestamp', 0),
'updaters': commit.get('updaters', []),
'components_affected': len(commit.get('changeDescriptions', []))
}
if duration >= CRITICAL_RENDER_THRESHOLD:
results['critical_commits'].append(commit_info)
elif duration >= VERY_SLOW_RENDER_THRESHOLD:
results['very_slow_commits'].append(commit_info)
elif duration >= SLOW_RENDER_THRESHOLD:
results['slow_commits'].append(commit_info)
# Track component-level stats
for updater in commit.get('updaters', []):
name = updater.get('displayName', 'Unknown')
results['component_stats'][name]['count'] += 1
results['component_stats'][name]['total_duration'] += duration
results['component_stats'][name]['max_duration'] = max(
results['component_stats'][name]['max_duration'],
duration
)
return results
def analyze_fiber_durations(data: dict) -> List[Tuple[str, float]]:
"""Analyze individual fiber (component) render durations"""
fiber_map = {}
# Build fiber ID to name mapping from snapshots
for root in data.get('dataForRoots', []):
snapshots = root.get('snapshots', [])
if isinstance(snapshots, list):
for snapshot_entry in snapshots:
if isinstance(snapshot_entry, list) and len(snapshot_entry) >= 2:
fiber_id = snapshot_entry[0]
fiber_data = snapshot_entry[1]
if isinstance(fiber_data, dict):
name = fiber_data.get('displayName', f'Fiber-{fiber_id}')
if name: # Only add if we have a display name
fiber_map[int(fiber_id)] = name
slow_fibers = []
for root in data.get('dataForRoots', []):
for commit in root.get('commitData', []):
for fiber_id, duration in commit.get('fiberActualDurations', []):
if duration >= SLOW_RENDER_THRESHOLD:
component_name = fiber_map.get(fiber_id, f'Component-{fiber_id}')
slow_fibers.append((component_name, duration, commit.get('timestamp', 0)))
return slow_fibers
def print_critical_issues(results: Dict, slow_fibers: List):
"""Print only the critical issues that need attention"""
print("\n" + "="*80)
print("🔴 CRITICAL PERFORMANCE ISSUES - ONBOARDING FAVORITE STACK")
print("="*80 + "\n")
# Critical renders (>100ms)
if results['critical_commits']:
print(f"⚠️ CRITICAL: {len(results['critical_commits'])} renders took >100ms (blocking UI)")
print("-" * 80)
for i, commit in enumerate(results['critical_commits'][:10], 1):
print(f" {i}. Duration: {commit['duration']:.2f}ms")
if commit['updaters']:
for updater in commit['updaters']:
print(f" Component: {updater.get('displayName', 'Unknown')}")
print(f" Affected components: {commit['components_affected']}")
print()
if len(results['critical_commits']) > 10:
print(f" ... and {len(results['critical_commits']) - 10} more critical renders\n")
# Very slow renders (50-100ms)
if results['very_slow_commits']:
print(f"\n⚠️ WARNING: {len(results['very_slow_commits'])} renders took 50-100ms (noticeable lag)")
print("-" * 80)
for i, commit in enumerate(results['very_slow_commits'][:5], 1):
print(f" {i}. Duration: {commit['duration']:.2f}ms")
if commit['updaters']:
for updater in commit['updaters']:
print(f" Component: {updater.get('displayName', 'Unknown')}")
print()
if len(results['very_slow_commits']) > 5:
print(f" ... and {len(results['very_slow_commits']) - 5} more slow renders\n")
# Top problematic components
print(f"\n📊 TOP 10 MOST EXPENSIVE COMPONENTS (by total time)")
print("-" * 80)
sorted_components = sorted(
results['component_stats'].items(),
key=lambda x: x[1]['total_duration'],
reverse=True
)[:10]
for i, (name, stats) in enumerate(sorted_components, 1):
avg_duration = stats['total_duration'] / stats['count'] if stats['count'] > 0 else 0
print(f" {i}. {name}")
print(f" Total time: {stats['total_duration']:.2f}ms across {stats['count']} updates")
print(f" Average: {avg_duration:.2f}ms | Max: {stats['max_duration']:.2f}ms")
print()
# Slowest individual component renders
if slow_fibers:
print(f"\n🐌 SLOWEST INDIVIDUAL COMPONENT RENDERS (>16ms)")
print("-" * 80)
sorted_fibers = sorted(slow_fibers, key=lambda x: x[1], reverse=True)[:15]
for i, (component, duration, timestamp) in enumerate(sorted_fibers, 1):
print(f" {i}. {component}: {duration:.2f}ms")
print()
# Summary statistics
print(f"\n📈 SUMMARY")
print("-" * 80)
print(f" Total renders analyzed: {results['total_commits']}")
print(f" Critical (>100ms): {len(results['critical_commits'])} ({len(results['critical_commits'])/results['total_commits']*100:.1f}%)")
print(f" Very Slow (50-100ms): {len(results['very_slow_commits'])} ({len(results['very_slow_commits'])/results['total_commits']*100:.1f}%)")
print(f" Slow (16-50ms): {len(results['slow_commits'])} ({len(results['slow_commits'])/results['total_commits']*100:.1f}%)")
print(f" Good (<16ms): {results['total_commits'] - len(results['critical_commits']) - len(results['very_slow_commits']) - len(results['slow_commits'])}")
print()
# Recommendations
print(f"\n💡 RECOMMENDATIONS")
print("-" * 80)
if results['critical_commits']:
print(" 1. URGENT: Focus on components causing >100ms renders first")
print(" These are blocking the UI and causing visible stuttering\n")
if len(results['very_slow_commits']) > 10:
print(" 2. Optimize components with 50-100ms renders")
print(" These cause noticeable lag during user interactions\n")
top_component = sorted_components[0] if sorted_components else None
if top_component and top_component[1]['count'] > 10:
print(f" 3. '{top_component[0]}' is updating frequently ({top_component[1]['count']} times)")
print(" Consider: React.memo, useMemo, or useCallback to reduce re-renders\n")
print(" 4. General optimizations to consider:")
print(" - Use FlatList/FlashList with proper key extraction")
print(" - Memoize expensive computations with useMemo")
print(" - Virtualize long lists")
print(" - Lazy load images and heavy components")
print(" - Use InteractionManager for non-urgent work")
print("\n" + "="*80 + "\n")
def main():
if len(sys.argv) < 2:
print("Usage: python3 analyze_performance.py <profiling-data-file.json>")
sys.exit(1)
filepath = sys.argv[1]
try:
data = load_profile_data(filepath)
print("🔍 Analyzing performance data...")
results = analyze_commits(data)
print("🔍 Analyzing component-level performance...")
slow_fibers = analyze_fiber_durations(data)
print_critical_issues(results, slow_fibers)
except FileNotFoundError:
print(f"❌ Error: Could not find file '{filepath}'")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"❌ Error: Invalid JSON in file - {e}")
sys.exit(1)
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == '__main__':
main() 메타데이터
- post_id
- 5277ac5354a1
- slug
- how-i-evolved-my-react-native-performance-debugging-and-how-i-measure-performance-optimizations-5277ac5354a1
- url
- https://medium.com/@karimhekal/how-i-evolved-my-react-native-performance-debugging-and-how-i-measure-performance-optimizations-5277ac5354a1
- canonical_url
- https://medium.com/@karimhekal/how-i-evolved-my-react-native-performance-debugging-and-how-i-measure-performance-optimizations-5277ac5354a1
- author_url
- https://medium.com/@karimhekal
- status
- ok
- fetched_at
- 2026-08-23 03:17:38