I was leading the architecture for a clinical AI platform at Synapsis Medical Technologies, where we were integrating real-time health data from wearables into a React Native environment. We were hitting a wall with the Bridge. As we scaled to handle high-frequency biometric streams, our UI thread started dropping frames during heavy data ingest.
The immediate internal pressure was to migrate everything to TurboModules and the New Architecture (React Native 0.76+). The promise of synchronous execution and type-safe Codegen is seductive. However, I had a team of 21 engineers and a product roadmap that didn't include a month of "architectural refactoring" with no visible feature output.
If you blindly migrate a stable Native Module to a TurboModule, you might spend 40 hours of engineering time to save 2ms of overhead that wasn't your bottleneck. I’ve shipped 18 production apps, and the most expensive mistake I see is optimising for architectural purity rather than measured latency.
Before you touch codegen, you need to know if the Bridge is actually your problem.
Why the Bridge fails under load
In the legacy architecture, every call between JavaScript and Native is asynchronous, serialised as JSON, and passed over a single queue.
When we were streaming data from a wearable heart-rate monitor, each data point was a Bridge message. If the user was also interacting with a complex UI—say, a real-time graph—the Bridge became a bottleneck. The symptom wasn't just "slowness"; it was non-deterministic lag. The JSON serialisation overhead for a large array of health metrics can take 5–10ms. When you do that 60 times a second, your message queue backs up, and your onPress events stay stuck behind data packets.
TurboModules solve this by using JSI (JavaScript Interface), allowing JS to hold a reference to C++ host objects. You move from "sending a letter" (Bridge) to "calling a function" (JSI). But if your module only fires once every 10 seconds—like a battery level check—the Bridge overhead is irrelevant.
The fix: Step-by-step instrumentation
Do not start by changing your C++ or Java code. Start by profiling the actual cost of the Bridge in your current production build.
1. Trace the Bridge Traffic
You need to see the volume and frequency of messages. Use the built-in MessageQueue spy. In your entry file (e.g., App.js), add:
import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue';
if (__DEV__) {
MessageQueue.spy(true);
}
Why: This logs every call between JS and Native to your console.
Confirm: Watch the console during the specific user action that feels slow. If you see a flood of calls to the same module (e.g., DeviceEventManager.emit), you have a high-frequency bottleneck. If you see very large JSON payloads, you have a serialisation bottleneck.
2. Measure Bridge Latency with Systrace
Standard console logs don't show you the time spent in the serialisation layer. Use the react-native-performance library or the built-in Profiler in Flipper.
- Open Flipper and select the React Native Tracers.
- Start a trace and perform the heavy action.
- Look for the
BridgeorJS Callmarkers.
Confirm: Look for gaps where the JS thread is idle but the Native thread is busy, or vice versa. If the "Bridge" bar is consistently wider than 5ms per call, the overhead is significant enough to justify a TurboModule.
3. Define the Codegen Spec
If the data proves you need to move, start with the Spec. This is the "contract" that ensures type safety. Create a file named Native[YourModuleName].ts in a specs folder.
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
readonly getHealthData: (id: string) => Promise<string>;
readonly streamData: (values: number[]) => void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('YourModuleName');
Why: Codegen uses this TypeScript interface to generate the C++ glue code (JSI) and the Java/Objective-C boilerplate.
Confirm: Run node node_modules/react-native/scripts/generate-codegen-artifacts.js. If it fails, your types aren't compatible with the restricted subset of TypeScript that Codegen supports (e.g., you cannot use generic any or complex unions).
4. Implement the Synchronous Method
One of the primary reasons I move to TurboModules is for synchronous methods. In the old Bridge, everything returned a Promise. In TurboModules, you can return a value directly.
In your native implementation (e.g., YourModule.mm for iOS):
- (NSString *)getHealthData:(NSString *)id {
return @"Synchronous Data";
}
Confirm: Call the method in JS: const data = YourModule.getHealthData('123');. If it returns the value immediately without an await, the JSI layer is working.
What it costs, and when not to do it
The cost of TurboModules is primarily in build-time complexity and developer experience.
- Build Times: Codegen adds a significant overhead to your build process. In our CI/CD overhaul, where I cut release cycles from 2 days to 4 hours, we found that poorly managed native builds could add 10 minutes just to the compilation phase of a clean build.
- C++ Requirement: To get the full performance benefit, you often end up writing C++ (via JSI). If your team is purely JS/React, you are introducing a massive bus-factor risk.
- Library Compatibility: Many third-party libraries still rely on the Bridge. Mixing the New Architecture with legacy modules requires the "Interop Layer," which can introduce its own performance regressions.
Do not migrate if: Your app is a standard CRUD app where the most complex thing you do is fetch a JSON API and display it in a list. The Bridge is more than fast enough for 90% of applications.
At your level
Starting out
Focus on understanding why MessageQueue.spy() exists. Before you learn how to write a TurboModule, learn how to read the Bridge traffic. If you can identify a "chatty" bridge, you've already provided more value than someone who just follows a tutorial.
Working engineer
Implement a performance monitoring suite. Don't wait for a bug report. Use react-native-performance to track bridge_transfer_time in your staging environment. The goal is to have a baseline so you can prove the ROI of a rewrite to your lead.
Senior or staff
Your job is to prevent the migration until it is necessary. Evaluate the "Interoperability Layer" cost. If you have 50 legacy modules, moving one to TurboModules might actually slow down the app due to the overhead of the New Architecture's compatibility bridge. Own the decision of when to flip the switch for the entire repo.
Lead or director
Consider the hiring implications. Moving to a JSI-heavy architecture means you need engineers who understand the memory management differences between JS and C++. If you don't have that talent or the budget to hire it, stick to the Bridge and optimise your JSON payloads instead.
In the interview
The Question: "We are seeing UI stutters when processing high-frequency data from a native sensor. How do you decide between optimising the Bridge or moving to TurboModules?"
Weak Answer: "TurboModules are faster because they use JSI and the New Architecture, so we should migrate to get better performance." This is weak because it assumes the architecture is the bottleneck without proof and ignores the massive engineering cost of migration.
Strong Answer: A strong answer starts with measurement. You should discuss using the MessageQueue spy to check for "chattiness" and measuring the serialisation overhead. You would mention that the Bridge overhead is usually around JSON stringification/parsing. If the frequency of calls is the issue, you might first try batching the data on the native side before it hits the Bridge.
The Senior/Staff Follow-up: "What happens if we have a mix of TurboModules and legacy modules?"
A senior candidate must discuss the Interoperability Layer. They should know that the New Architecture has to wrap legacy modules to make them compatible, which can actually increase memory pressure. They should be able to explain that the real win of TurboModules isn't just "speed," but the ability to perform synchronous calls to native code, which eliminates the need for complex async state management in some UI interactions.
Amit Chakraborty is a founding engineer and senior architect — React Native, AI/RAG systems and production architecture. Portfolio: www.amitchakraborty.dev · LinkedIn · GitHub. Open to senior and founding engineering roles, remote worldwide.
Top comments (0)