Old Architecture vs New Architecture

📖 Concept

React Native's architecture underwent a complete overhaul starting with the New Architecture (released stable in RN 0.76). Understanding both is critical because most production apps still use the old architecture or are mid-migration.

Old Architecture (Bridge-based):

JS Thread ←→ [Bridge (JSON serialization)] ←→ Native Thread(s)
  • 3 threads: JS Thread, Main/UI Thread, Shadow Thread (layout)
  • Bridge: All communication between JS and Native goes through an asynchronous bridge
  • Serialization: Every message is serialized to JSON, sent across the bridge, deserialized on the other side
  • Batching: Messages are batched (typically every 5ms) to reduce overhead
  • Asynchronous: All bridge communication is async — you can't call a native method and get a synchronous return value

Problems with the Old Architecture:

  1. Serialization overhead: Every object crossing the bridge is JSON.stringify'd → sent → JSON.parse'd
  2. Asynchronous only: Can't do synchronous native calls (needed for gestures, layout measurements)
  3. Single-threaded bridge: Bottleneck when many messages are in flight
  4. Startup cost: All native modules are initialized at startup, even unused ones
  5. No type safety: Bridge messages are untyped JSON — runtime crashes instead of compile errors

New Architecture:

JS Thread ←→ [JSI (C++ direct binding)] ←→ Native Thread(s)
                                              ↓
                                    [Fabric Renderer]
                                    [TurboModules]
  • JSI (JavaScript Interface): C++ API that lets JS directly call native methods — no serialization, no bridge
  • TurboModules: Lazy-loaded native modules with type-safe interfaces (codegen from TypeScript specs)
  • Fabric: New rendering system that enables synchronous layout, concurrent rendering, and direct C++ manipulation of the view tree
  • Codegen: TypeScript specs auto-generate native interfaces — type safety across JS ↔ Native boundary

Key improvements:

Aspect Old Architecture New Architecture
Communication Async bridge (JSON) Direct JSI (C++ refs)
Module loading All at startup Lazy (on first use)
Type safety None (JSON) Codegen from TS specs
Synchronous calls Impossible Supported
Rendering Async only Sync + async
Layout Shadow thread Can run on any thread
Startup time Slow (all modules init) Fast (lazy modules)

💻 Code Example

codeTap to expand ⛶
1// === OLD ARCHITECTURE: Bridge Communication ===
2
3// How a native module call works in the OLD architecture:
4// 1. JS calls NativeModules.MyModule.getData()
5// 2. This creates a JSON message: {"module":"MyModule","method":"getData","args":[]}
6// 3. Message is queued in the bridge buffer
7// 4. Bridge flushes (every ~5ms or 100 messages)
8// 5. Native receives JSON, deserializes, finds module, calls method
9// 6. Result serialized to JSON, sent back through bridge
10// 7. JS receives, deserializes, resolves the Promise
11
12// Total overhead per call:
13// 2x JSON serialization + 2x bridge crossing + async scheduling
14// For a 1KB object: ~0.5-2ms per call
15// For 100 calls/frame: 50-200ms → DROPPED FRAMES
16
17// === NEW ARCHITECTURE: JSI Direct Binding ===
18
19// How JSI works:
20// JS objects hold direct C++ references (HostObjects)
21// No serialization, no bridge, no async overhead
22
23// TurboModule spec (TypeScript → generates native interfaces)
24// specs/NativeDeviceInfo.ts
25import type { TurboModule } from 'react-native';
26import { TurboModuleRegistry } from 'react-native';
27
28export interface Spec extends TurboModule {
29 // Codegen generates native implementations from these signatures
30 getDeviceName(): string; // Synchronous! (impossible in old arch)
31 getBatteryLevel(): Promise<number>; // Async when needed
32 getConstants(): {
33 platform: string;
34 version: string;
35 isTablet: boolean;
36 };
37}
38
39export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');
40
41// Usage — looks the same to the developer:
42import DeviceInfo from './specs/NativeDeviceInfo';
43
44const name = DeviceInfo.getDeviceName(); // SYNCHRONOUS call via JSI
45const battery = await DeviceInfo.getBatteryLevel(); // Async when appropriate
46
47// === FABRIC RENDERER ===
48
49// Old architecture rendering:
50// JS creates React tree → Shadow tree (layout on shadow thread)
51// → bridge message → UI thread creates native views
52// All async, all through bridge
53
54// New architecture (Fabric):
55// JS creates React tree → Fabric creates shadow tree in C++
56// → Yoga computes layout (can run on ANY thread)
57// → Directly mutates native view tree (no bridge)
58// → Enables synchronous measurements and concurrent rendering
59
60// Fabric component spec:
61// specs/NativeCustomView.ts
62import type { ViewProps } from 'react-native';
63import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
64
65interface NativeProps extends ViewProps {
66 color: string;
67 borderRadius?: number;
68 onCustomEvent?: (event: { value: string }) => void;
69}
70
71export default codegenNativeComponent<NativeProps>('CustomView');
72
73// Why synchronous matters for performance:
74// Gesture handling: touch → measure → transform → draw
75// Old arch: touch → bridge → measure → bridge → transform → bridge → draw
76// (3 async round-trips = visible lag)
77// New arch: touch → measure → transform → draw (all synchronous via JSI)

🏋️ Practice Exercise

Architecture Deep Dive:

  1. Enable the New Architecture in a React Native 0.76+ project and compare startup times
  2. Create a TurboModule spec and observe the auto-generated native code
  3. Profile bridge traffic in the old architecture using MessageQueue spy
  4. Measure the performance difference between a bridge-based native module call and a JSI-based one
  5. Create a Fabric component with codegen and compare it to the old createNativeComponent approach
  6. Document the migration steps needed to move your app from old to new architecture

⚠️ Common Mistakes

  • Assuming the new architecture is automatically faster — it enables faster patterns but you still need to use them correctly

  • Not testing third-party libraries for new architecture compatibility — many libraries still rely on the bridge

  • Enabling new architecture without updating native modules — old modules work via an interop layer but with degraded performance

  • Not understanding that JSI doesn't mean everything is synchronous — you should still use async for expensive operations to avoid blocking the JS thread

  • Ignoring codegen type specs — writing native modules without specs loses the type safety benefit of the new architecture

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Old Architecture vs New Architecture. Login to unlock this feature.