Threading Model & Data Flow
📖 Concept
React Native runs on multiple threads, and understanding the threading model is essential for debugging performance issues, race conditions, and ANR (Application Not Responding) errors.
Thread overview:
JS Thread — Runs the JavaScript engine (Hermes/JSC). Executes your React components, business logic, state management, API calls. Single-threaded — only one JS operation at a time.
Main/UI Thread — The native platform's main thread. Handles native UI rendering, touch events, native animations. On Android, blocking this thread for >5s triggers an ANR dialog.
Shadow Thread (Old Architecture) — Runs Yoga layout calculations in the background. Computes flexbox layout and sends results to the UI thread. In the new architecture, layout can run on any thread.
Native Modules Thread(s) — Custom background threads for native module work (file I/O, image processing, etc.)
Data flow in the old architecture:
User taps button (UI Thread)
→ Touch event serialized to JSON
→ Bridge → JS Thread
→ React processes event, updates state
→ New render tree created
→ Diff computed (reconciliation)
→ Bridge → Shadow Thread
→ Yoga computes layout
→ Bridge → UI Thread
→ Native views updated
Data flow in the new architecture (Fabric):
User taps button (UI Thread)
→ JSI → JS Thread (synchronous via JSI)
→ React processes event
→ Fabric creates shadow tree in C++
→ Yoga computes layout (can be on any thread)
→ Fabric directly updates native views
Where bottlenecks happen:
- JS Thread overload — Heavy computation (JSON parsing, data transformation, complex rendering) blocks event processing
- Bridge congestion — Too many messages/frame causes queue buildup and delayed responses (old arch)
- UI Thread blocking — Heavy native operations (image decoding, custom drawing) cause frames to drop
- Thread coordination — Race conditions between JS and native state updates
💻 Code Example
1// === THREAD AWARENESS IN PRACTICE ===23// 1. Detecting JS Thread blocking4// If the JS thread is blocked, ALL of these stop working:5// - setTimeout/setInterval callbacks6// - Promise resolutions7// - React state updates8// - Touch event processing (events queue up)910// Diagnostic: JS thread block detector11let lastTick = Date.now();12setInterval(() => {13 const now = Date.now();14 const elapsed = now - lastTick;15 if (elapsed > 100) { // Expected: ~16ms16 console.warn(`JS Thread blocked for ${elapsed}ms`);17 // In production: report to analytics18 }19 lastTick = now;20}, 16);2122// 2. Moving work OFF the JS thread2324// ❌ Blocking the JS thread with heavy computation25function processLargeDataset(data: any[]) {26 // This runs on the JS thread — blocks UI for 500ms+27 return data28 .filter(item => complexPredicate(item))29 .map(item => heavyTransformation(item))30 .sort((a, b) => a.score - b.score);31}3233// ✅ Use InteractionManager to defer34import { InteractionManager } from 'react-native';3536async function processLargeDatasetAsync(data: any[]) {37 // Wait for animations/transitions to complete38 await InteractionManager.runAfterInteractions();3940 // Process in chunks to keep JS thread responsive41 const CHUNK_SIZE = 100;42 const results = [];4344 for (let i = 0; i < data.length; i += CHUNK_SIZE) {45 const chunk = data.slice(i, i + CHUNK_SIZE);46 const processed = chunk47 .filter(item => complexPredicate(item))48 .map(item => heavyTransformation(item));49 results.push(...processed);5051 // Yield to event loop between chunks52 await new Promise(resolve => setTimeout(resolve, 0));53 }5455 return results.sort((a, b) => a.score - b.score);56}5758// 3. Native-driven animations bypass JS thread59import Animated from 'react-native-reanimated';6061// Reanimated runs animations on the UI thread via worklets62// JS thread can be completely blocked and animations still run smooth63const animatedStyle = useAnimatedStyle(() => {64 'worklet'; // This code runs on the UI thread, not JS thread65 return {66 transform: [{ translateX: withSpring(offset.value) }],67 opacity: interpolate(offset.value, [0, 100], [1, 0]),68 };69});7071// 4. Thread-safe communication patterns72// Using shared values (Reanimated) for cross-thread data73const scrollOffset = useSharedValue(0);7475// UI thread (gesture handler worklet)76const gestureHandler = useAnimatedGestureHandler({77 onActive: (event) => {78 'worklet';79 scrollOffset.value = event.translationY; // UI thread80 },81 onEnd: () => {82 'worklet';83 // Trigger JS thread callback for state update84 runOnJS(updateState)(scrollOffset.value); // Cross-thread safely85 },86});
🏋️ Practice Exercise
Threading Exercises:
- Add the JS thread block detector above to your app — identify what operations cause >50ms blocks
- Profile your app using Flipper's Performance plugin — identify which thread is the bottleneck
- Move a heavy computation to a web worker or native module and compare JS thread responsiveness
- Create a Reanimated worklet animation that runs while the JS thread is deliberately blocked with a heavy loop
- Implement chunked data processing that yields to the event loop between chunks
- Measure the difference in touch responsiveness with and without a heavy JS computation running
⚠️ Common Mistakes
Blocking the JS thread with synchronous operations — JSON.parse of large payloads, complex sorting, heavy regex operations
Assuming React Native animations run on the JS thread — Animated with useNativeDriver and Reanimated run on the UI thread
Not using InteractionManager.runAfterInteractions — expensive work during navigation transitions causes janky animations
Running heavy native operations on the main thread — image processing, file I/O should use background threads
Not understanding that setInterval in JS doesn't guarantee timing — if the JS thread is busy, intervals are delayed, not dropped
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Threading Model & Data Flow. Login to unlock this feature.