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:

  1. 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.

  2. 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.

  3. 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.

  4. 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:

  1. JS Thread overload — Heavy computation (JSON parsing, data transformation, complex rendering) blocks event processing
  2. Bridge congestion — Too many messages/frame causes queue buildup and delayed responses (old arch)
  3. UI Thread blocking — Heavy native operations (image decoding, custom drawing) cause frames to drop
  4. Thread coordination — Race conditions between JS and native state updates

💻 Code Example

codeTap to expand ⛶
1// === THREAD AWARENESS IN PRACTICE ===
2
3// 1. Detecting JS Thread blocking
4// If the JS thread is blocked, ALL of these stop working:
5// - setTimeout/setInterval callbacks
6// - Promise resolutions
7// - React state updates
8// - Touch event processing (events queue up)
9
10// Diagnostic: JS thread block detector
11let lastTick = Date.now();
12setInterval(() => {
13 const now = Date.now();
14 const elapsed = now - lastTick;
15 if (elapsed > 100) { // Expected: ~16ms
16 console.warn(`JS Thread blocked for ${elapsed}ms`);
17 // In production: report to analytics
18 }
19 lastTick = now;
20}, 16);
21
22// 2. Moving work OFF the JS thread
23
24// ❌ Blocking the JS thread with heavy computation
25function processLargeDataset(data: any[]) {
26 // This runs on the JS thread — blocks UI for 500ms+
27 return data
28 .filter(item => complexPredicate(item))
29 .map(item => heavyTransformation(item))
30 .sort((a, b) => a.score - b.score);
31}
32
33// ✅ Use InteractionManager to defer
34import { InteractionManager } from 'react-native';
35
36async function processLargeDatasetAsync(data: any[]) {
37 // Wait for animations/transitions to complete
38 await InteractionManager.runAfterInteractions();
39
40 // Process in chunks to keep JS thread responsive
41 const CHUNK_SIZE = 100;
42 const results = [];
43
44 for (let i = 0; i < data.length; i += CHUNK_SIZE) {
45 const chunk = data.slice(i, i + CHUNK_SIZE);
46 const processed = chunk
47 .filter(item => complexPredicate(item))
48 .map(item => heavyTransformation(item));
49 results.push(...processed);
50
51 // Yield to event loop between chunks
52 await new Promise(resolve => setTimeout(resolve, 0));
53 }
54
55 return results.sort((a, b) => a.score - b.score);
56}
57
58// 3. Native-driven animations bypass JS thread
59import Animated from 'react-native-reanimated';
60
61// Reanimated runs animations on the UI thread via worklets
62// JS thread can be completely blocked and animations still run smooth
63const animatedStyle = useAnimatedStyle(() => {
64 'worklet'; // This code runs on the UI thread, not JS thread
65 return {
66 transform: [{ translateX: withSpring(offset.value) }],
67 opacity: interpolate(offset.value, [0, 100], [1, 0]),
68 };
69});
70
71// 4. Thread-safe communication patterns
72// Using shared values (Reanimated) for cross-thread data
73const scrollOffset = useSharedValue(0);
74
75// UI thread (gesture handler worklet)
76const gestureHandler = useAnimatedGestureHandler({
77 onActive: (event) => {
78 'worklet';
79 scrollOffset.value = event.translationY; // UI thread
80 },
81 onEnd: () => {
82 'worklet';
83 // Trigger JS thread callback for state update
84 runOnJS(updateState)(scrollOffset.value); // Cross-thread safely
85 },
86});

🏋️ Practice Exercise

Threading Exercises:

  1. Add the JS thread block detector above to your app — identify what operations cause >50ms blocks
  2. Profile your app using Flipper's Performance plugin — identify which thread is the bottleneck
  3. Move a heavy computation to a web worker or native module and compare JS thread responsiveness
  4. Create a Reanimated worklet animation that runs while the JS thread is deliberately blocked with a heavy loop
  5. Implement chunked data processing that yields to the event loop between chunks
  6. 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.