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:
- Serialization overhead: Every object crossing the bridge is JSON.stringify'd → sent → JSON.parse'd
- Asynchronous only: Can't do synchronous native calls (needed for gestures, layout measurements)
- Single-threaded bridge: Bottleneck when many messages are in flight
- Startup cost: All native modules are initialized at startup, even unused ones
- 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
1// === OLD ARCHITECTURE: Bridge Communication ===23// 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 buffer7// 4. Bridge flushes (every ~5ms or 100 messages)8// 5. Native receives JSON, deserializes, finds module, calls method9// 6. Result serialized to JSON, sent back through bridge10// 7. JS receives, deserializes, resolves the Promise1112// Total overhead per call:13// 2x JSON serialization + 2x bridge crossing + async scheduling14// For a 1KB object: ~0.5-2ms per call15// For 100 calls/frame: 50-200ms → DROPPED FRAMES1617// === NEW ARCHITECTURE: JSI Direct Binding ===1819// How JSI works:20// JS objects hold direct C++ references (HostObjects)21// No serialization, no bridge, no async overhead2223// TurboModule spec (TypeScript → generates native interfaces)24// specs/NativeDeviceInfo.ts25import type { TurboModule } from 'react-native';26import { TurboModuleRegistry } from 'react-native';2728export interface Spec extends TurboModule {29 // Codegen generates native implementations from these signatures30 getDeviceName(): string; // Synchronous! (impossible in old arch)31 getBatteryLevel(): Promise<number>; // Async when needed32 getConstants(): {33 platform: string;34 version: string;35 isTablet: boolean;36 };37}3839export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');4041// Usage — looks the same to the developer:42import DeviceInfo from './specs/NativeDeviceInfo';4344const name = DeviceInfo.getDeviceName(); // SYNCHRONOUS call via JSI45const battery = await DeviceInfo.getBatteryLevel(); // Async when appropriate4647// === FABRIC RENDERER ===4849// Old architecture rendering:50// JS creates React tree → Shadow tree (layout on shadow thread)51// → bridge message → UI thread creates native views52// All async, all through bridge5354// 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 rendering5960// Fabric component spec:61// specs/NativeCustomView.ts62import type { ViewProps } from 'react-native';63import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';6465interface NativeProps extends ViewProps {66 color: string;67 borderRadius?: number;68 onCustomEvent?: (event: { value: string }) => void;69}7071export default codegenNativeComponent<NativeProps>('CustomView');7273// Why synchronous matters for performance:74// Gesture handling: touch → measure → transform → draw75// Old arch: touch → bridge → measure → bridge → transform → bridge → draw76// (3 async round-trips = visible lag)77// New arch: touch → measure → transform → draw (all synchronous via JSI)
🏋️ Practice Exercise
Architecture Deep Dive:
- Enable the New Architecture in a React Native 0.76+ project and compare startup times
- Create a TurboModule spec and observe the auto-generated native code
- Profile bridge traffic in the old architecture using MessageQueue spy
- Measure the performance difference between a bridge-based native module call and a JSI-based one
- Create a Fabric component with codegen and compare it to the old createNativeComponent approach
- 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.