RoadmapDay 31 / 80
React NativeMonth 2 · Week 7

Day 31: RN Old Architecture: Bridge, Serialization & Async Bottlenecks

Understand exactly why the classic React Native Bridge causes jank — the async, JSON-serialized, batched messaging model — before comparing it to JSI.

Mark this day complete

Study

Concepts

Three threads, one async messaging Bridge

Classic React Native runs (at least) three threads: the JS thread (your React code, running in JavaScriptCore), the Native/UI (Shadow) thread that computes layout via Yoga and talks to native views, and the Main/UI thread that actually owns iOS/Android views. The Bridge is the ONLY channel between the JS thread and the native side — every call across it (invoking a native module, updating a view's props) is serialized to JSON, queued, and sent asynchronously in batches, then deserialized on the other side.

This design was intentional: it fully decouples JS from native (no shared memory, easier to reason about, easier to run JS off-thread), but it means EVERY cross-boundary interaction pays a serialization + queueing + async round-trip cost — fine for occasional calls, a real bottleneck for anything high-frequency.

Where the bottleneck actually shows up

Gesture-driven UI (drag, swipe, scroll-linked animations) needs to update native view properties on every frame (~16.6ms budget at 60fps). Routing that through JSON serialization + an async Bridge round-trip per frame is where classic RN visibly drops frames — this is precisely the problem `react-native-reanimated`'s worklet model and `react-native-gesture-handler` were built to route around, by running logic closer to the UI thread instead of bouncing through the Bridge per frame.

Because the Bridge is asynchronous by construction, calling a native method never returns a value directly — it always goes through a callback/Promise, which is fine for network-style calls (fetch a file, read from storage) but forces awkward patterns for anything that conceptually wants a synchronous answer (e.g. "is this native view currently visible?").

See It

Visualizations

Visualization

Old Architecture: three threads talking through the Bridge

JS ThreadReact, your app code (JavaScriptCore)
The Bridgeasync, batched, JSON-serialized messages
Native/UI ThreadYoga layout + real iOS/Android views

Build It

Code Examples

Every classic native module call is inherently async

js
import { NativeModules } from 'react-native';
const { DeviceInfo } = NativeModules;

// Cannot get a value back synchronously — must cross the Bridge
// asynchronously even for data that is already available natively.
async function readBatteryLevel() {
  const level = await DeviceInfo.getBatteryLevel(); // serialize → queue → deserialize
  console.log('battery:', level);
}

// A gesture-driven animation naively calling setNativeProps on every
// touch move pays this round-trip cost on every single frame:
function onTouchMove(event) {
  viewRef.current.setNativeProps({
    style: { transform: [{ translateX: event.nativeEvent.pageX }] },
  }); // -> JSON-serialized, queued, sent over the Bridge, every frame
}

At 60fps you have ~16.6ms per frame for JS logic, layout, AND this round trip — any Bridge congestion (large batched payloads, other pending calls) can push you past that budget and drop frames.

Remember

Key Takeaways

  • Classic RN has 3 threads (JS, Shadow/layout, Native UI) connected only by the asynchronous, JSON-serializing Bridge.
  • Every cross-boundary call pays a serialize → queue → async-deliver → deserialize cost, batched for efficiency but never free.
  • High-frequency interactions (gestures, per-frame animation) are where this cost becomes visibly janky.
  • The Bridge is asynchronous by design — even conceptually "instant" native reads must go through a callback/Promise.
  • This exact bottleneck is the direct motivation for the New Architecture (JSI, Day 32) — know the "why" before the "what".

Do It

Practice

  1. 1Read the RN docs page on the legacy Bridge and diagram the object/message shape of one batched call by hand.
  2. 2Profile a naive setNativeProps-per-touch-move animation with the RN Perf Monitor and note dropped frames under load.
  3. 3List three real product features (from apps you use) that would visibly suffer from Bridge-round-trip-per-frame costs.