Every few months, someone writes a blog post about switching from React Native to native, or from native to React Native. The comment section turns into a religious war. Both sides are wrong — not because they don't have valid arguments, but because they're arguing about a choice that doesn't need to be binary.

Our thesis is simple: start React Native. Go native where it hurts. Not a compromise. An engineering strategy that optimizes for the only thing that matters when you're a small team: the speed at which you can learn whether your product works.

The math that settles it for small teams

If you go fully native, you need two codebases. Two sets of bugs. Two implementations of every feature. Two developers who need to be synchronized on every product decision. For a team of 3-5 engineers, that's not a minor overhead — it's the difference between shipping a feature in 2 days and shipping it in 6.

React Native gives you one codebase for both platforms. In practice, about 85-95% of the code is shared between iOS and Android. The remaining 5-15% is platform-specific — permissions, some UI behaviors, navigation edge cases. That's the real ratio, not the "write once run anywhere" marketing pitch, and not the "everything breaks on Android" horror story.

FULLY NATIVE — 2 codebases iOS (Swift/UIKit) Android (Kotlin) ×2 effort REACT NATIVE — 1 codebase shared code — 90% ios drd fig. 1 — code distribution: native vs React Native

The apps that prove the point

The "React Native isn't production-ready" argument died years ago, but people keep repeating it. Here's a non-exhaustive list of apps built on React Native that you've probably used this week:

AppCategoryScale
InstagramSocial2B+ users
ShopifyE-commerce$200B+ GMV
DiscordCommunication200M+ MAU
CoinbaseFinance110M+ users
Microsoft TeamsProductivity320M+ MAU
FlipkartE-commerce500M+ downloads
BloombergFinanceEnterprise
WalmartRetail150M+ users

These aren't toy apps. Instagram runs React Native at a scale that would break most native implementations. Discord handles real-time voice, video, and messaging. Coinbase handles actual money. The framework isn't the bottleneck — your architecture is.

The New Architecture changed everything

The old React Native had a genuine performance problem: the JavaScript bridge. Every communication between JS and native went through an asynchronous, serialized bridge that added latency. Animations could stutter. Lists could jank. The criticism was valid.

React Native's New Architecture — enabled by default since RN 0.76 — replaced the bridge with JSI (JavaScript Interface), which allows direct synchronous calls between JS and native. Fabric, the new rendering system, renders UI synchronously on the main thread. TurboModules load native modules lazily, cutting startup time.

The performance gap between React Native and native has narrowed to the point where, for 95% of consumer app interactions, it's imperceptible. We're talking about differences measured in single-digit milliseconds — invisible to the human eye, irrelevant to the user experience.

OLD ARCHITECTURE JS Thread Bridge async, serialized Native NEW ARCHITECTURE (0.76+) JS Thread JSI Fabric sync rendering Native direct sync fig. 2 — async bridge → synchronous JSI

Expo made the DX argument irrelevant

The second historical argument against React Native was developer experience. Xcode builds, Gradle nightmares, native dependency hell. Debugging required switching between three different toolchains. Setting up a CI/CD pipeline was a multi-week project.

Expo solved all of this. Not partially — completely. EAS Build handles cloud builds for both platforms. EAS Submit pushes to both stores. Expo Router gives you file-based routing that feels like Next.js. Config plugins handle native configuration without ever touching Xcode or Android Studio. Over-the-air updates let you push JS changes without going through app review.

Our current workflow: write code, push to GitHub, EAS builds both platforms in the cloud, submits to both stores, and optionally pushes an OTA update to existing users — all from a single git push. No Mac required for Android. No Android Studio required for anything. The build infrastructure that used to take a senior engineer a month to set up is now a YAML file.

The Android tax

Here's something nobody talks about openly: Android development is significantly harder than iOS development. Not because of the language or the tooling, but because of fragmentation. There are 24,000+ distinct Android device models in the wild. Screen sizes range from 4" to foldables. OS versions from Android 8 to 15 coexist. OEM skins from Samsung, Xiaomi, Huawei, and others introduce unpredictable behavior in notifications, background processes, and permissions.

If you're a small team going fully native, Android is where your velocity dies. You'll spend 40% of your time dealing with device-specific bugs that affect 2% of your user base. React Native abstracts away most of this fragmentation. Not all of it — camera handling and notification behavior still vary — but enough that your team can focus on product instead of Samsung-specific edge cases.

The question isn't "can I build this in React Native?" The question is "where in my app would the user actually notice the difference?" For most apps, the honest answer is: almost nowhere.

When to go native: the hotspot model

Here's where Antoine's thesis becomes nuanced. React Native is the right default, but some parts of your app deserve native implementation. The key word is "some" — not "all."

We call it the hotspot model. Profile your app. Find the interactions that users perform hundreds of times per session. The ones where 3ms of latency compounds into a perceptible difference. Those — and only those — get native modules.

YOUR APP Onboarding React Native Settings React Native Profile React Native Chat / Messaging React Native Swipe Engine NATIVE MODULE — 500+ interactions/session Search / Filters React Native Paywall React Native fig. 3 — hotspot model: 90% RN, native only where frequency demands it

Take a dating app. The swipe gesture is the core interaction. Users do it hundreds of times per session. Every frame matters. The physics of the card movement, the gesture response time, the animation when a card is dismissed — all of this needs to be at 60fps, no exceptions. That's a native module.

But the settings screen? The profile editor? The chat? The paywall? The onboarding flow? None of these require native performance. They're accessed occasionally, used briefly, and the user wouldn't notice a 5ms difference. Building them in React Native saves weeks of development time with zero user-facing cost.

The ratio in our dating app: 92% React Native, 8% native modules. That 8% covers the swipe engine, the image preloading pipeline, and a custom camera module. Everything else is cross-platform.

The migration path is real

One of the underappreciated features of React Native's architecture is that native modules are first-class citizens. You can write a native module in Swift or Kotlin, expose it to JavaScript via TurboModules, and use it seamlessly alongside React Native components. There's no hard boundary between "the RN parts" and "the native parts."

This means the migration path from "full React Native" to "React Native with native hotspots" is incremental. You don't rewrite. You identify the component that needs better performance, write a native module, swap it in, and the rest of the app doesn't change. We've done this three times, and each migration took less than a week.

Going in the other direction — from fully native to React Native — is a rewrite. It takes months. It's risky. It breaks things. This asymmetry is the strongest argument for starting with React Native: you can always go native later, one module at a time. You can never go cross-platform later without rewriting everything.

The numbers from our codebase

MetricOur RN appIndustry native avg
Time to MVP6 weeks14 weeks
Code shared iOS/Android92%0%
Deploy frequency12x/day (OTA)1-2x/week
Cold start time1.2s0.8s
Scroll FPS (lists)58-6060
Swipe FPS (native module)6060
Bundle size (iOS)42MB35MB
Crash rate0.12%0.08%

Cold start is 400ms slower. Bundle size is 7MB larger. Crash rate is marginally higher. Those are the real costs. For a team shipping 12 OTA updates per day instead of waiting for App Store review, the tradeoff isn't even close.


The debate isn't React Native vs Native. It's "how fast can you find product-market fit" vs "how polished is your scroll performance." If you're a team of 50 engineers at Meta, go native. You have the headcount to maintain two codebases and the scale to justify the investment. If you're a team of 3-16 building consumer products where cycle time is the competitive advantage — start React Native, profile your hotspots, go native where it matters. That's not a compromise. That's engineering.