Flutter vs React Native vs native: a decision framework for 2026
A comparison written to be read by an engineer who loves whichever option you like least. It covers how each framework really draws your interface, which performance claims survive scrutiny, and the store-level constraints that quietly rule options out.
There are three real options, and a fourth worth knowing about
The framing “Flutter or React Native” quietly assumes the decision to go cross-platform has already been made. It usually has not, and it is the more consequential choice. So the honest list is:
- Native. Two codebases: Swift with SwiftUI on Apple platforms, Kotlin with Jetpack Compose on Android. Both vendors now recommend their declarative toolkit as the default way to build a UI, which means the ergonomics gap between native and cross-platform has narrowed from the native side too.
- Flutter. One Dart codebase, rendering everything itself onto a canvas.
- React Native. One JavaScript or TypeScript codebase, driving real platform views.
- Kotlin Multiplatform, the fourth option, which shares business logic across platforms while leaving each interface native. It suits teams who already have two native apps and want to stop writing the networking layer twice. It is a different bet — share the boring parts, keep the visible parts native — and it deserves a place on your shortlist even though it is outside this comparison.
The rest of this article is about how to choose between the first three without relying on anybody’s benchmark video.
How each one actually renders your interface
This is the single most useful thing to understand, because nearly every downstream difference — performance characteristics, look and feel, what breaks on an OS update — follows from it.
Native: the platform’s own toolkit
Your code calls the platform’s UI framework directly. A button is that platform’s button, with that platform’s accessibility behaviour, text selection, dynamic type, haptics and animation curves already correct. When Apple or Google ships a new OS with a new visual language, your app largely inherits it. There is no translation layer, so there is nothing to be out of date.
Flutter: its own renderer, all the way down
Flutter does not use platform widgets. It ships a rendering engine and draws every pixel itself, which is why a Flutter app looks identical on both platforms unless you deliberately make it not. Since the move to Impeller, shaders are precompiled when the engine is built rather than compiled at runtime, which removed the first-run animation stutter that was Flutter’s most legitimate historic criticism. Impeller is the only renderer on iOS, where it uses Metal; on Android it is the default from API level 29 using Vulkan, falling back to OpenGL on older or unsupported devices.
The trade is exactly what you would expect from owning the renderer: total visual control, complete consistency, and a permanent obligation to reimplement platform behaviours that native apps get for free. Text editing, accessibility semantics and system-level interactions are Flutter’s implementations of those things, and they are good, and they are not the platform’s.
React Native: JavaScript driving real platform views
React Native runs your JavaScript in an embedded engine and uses it to instruct the platform to create and update its own native views. A React Native button really is a platform view.
The New Architecture — Fabric for rendering, TurboModules for native modules, and JSI underneath both — replaced the old asynchronous bridge that serialised every message between JavaScript and native code. JSI lets JavaScript hold direct references to C++ objects, which removes the serialisation cost that caused most of the framework’s historic performance complaints, and enables synchronous layout measurement. It has been the default since React Native 0.76. Hermes, the default JavaScript engine, is built for this job specifically: precompiled bytecode in release builds, which the documentation credits with improved start-up time, lower memory use and smaller app size than the alternative.
If you evaluated React Native before the New Architecture and rejected it, you evaluated a different product. That is worth restating, because a lot of the received wisdom about React Native performance is describing an architecture that is no longer the default.
Performance: where the difference is real, and where it is folklore
For a normal business application — lists, forms, navigation, network calls, a bit of animation — all three deliver an experience users cannot distinguish, and the deciding factor is the competence of the team rather than the framework. That is the boring truth that benchmark videos are structured to avoid.
Where genuine differences remain:
| Workload | Native | Flutter | React Native |
|---|---|---|---|
| Long lists with heavy cell content | Best, with platform recycling | Very good | Good; virtualisation needs care |
| Complex custom animation and effects | Best | Very good; you own the renderer | Adequate; needs a native driver for the smoothest results |
| Heavy computation on device | Best | Good; Dart compiles ahead of time | Weakest on the JS thread; push work native or to a worker |
| Camera, sensors, background work | Best; direct API access | Needs a plugin or a platform channel | Needs a module or a native implementation |
| Cold start | Best | Engine initialisation adds a fixed cost | Engine plus bundle load adds a fixed cost |
| Interface that must look native on both | Free | Deliberate work | Largely free |
The last row is more often the real issue than raw speed. Flutter’s consistency is an advantage when you want one brand-led interface everywhere and a liability when your users expect their platform’s conventions. Neither is a defect; they are different products.
Two claims worth retiring. First, “Flutter is faster because it compiles to native code” — Dart’s ahead-of-time compilation is genuine, but rendering, not language execution, dominates perceived performance in most apps. Second, “React Native is slow because of the bridge” — the bridge is not in the default architecture any more. Both statements were once useful shorthand and are now just repeated.
Cost: where the saving is real, and why the percentages are invented
You will see a cross-platform saving quoted as a specific range. Nobody has published a credible measurement that supports any particular figure, the studies cited never survive being clicked through, and the number varies with who is selling. We are not going to add another one.
The mechanism, though, is real and can be reasoned about without a statistic:
Where you genuinely save. One interface implementation rather than two. One set of business rules, validation and state management. One backlog, one set of code reviews, one release process to design. Crucially, every subsequent change is made once — and over a multi-year product life, ongoing change dwarfs the initial build.
Where the saving leaks back out. You still test on two platforms and two sets of devices, and QA barely shrinks. Every native capability you need — camera, Bluetooth, background location, health data, widgets, payment sheets — requires either a maintained plugin or your own native implementation on both sides, which is native work you did not budget for. And the second-order cost: when something breaks deep in the platform layer, you need someone who can read a native stack trace, and if nobody on the team can, an afternoon’s bug becomes a fortnight.
Where cross-platform costs more than native. Applications dominated by device integration, real-time media, or platform-specific interface conventions. Here you write the hard parts twice anyway, and pay an abstraction tax on top.
The framing that actually helps a budget conversation: cross-platform reduces the number of times you implement each feature, and does not reduce the number of platforms you have to support. Any estimate that ignores the second half is optimistic.
Hiring, ecosystem risk and who owns the framework
Framework choice is a hiring decision with a five-year tail, and it is worth looking at governance rather than GitHub stars.
React Native’s position changed materially in 2026: React and React Native are now owned by the React Foundation, an independent foundation hosted by the Linux Foundation, with eight platinum founding members — Amazon, Callstack, Expo, Huawei, Meta, Microsoft, Software Mansion and Vercel — and technical direction deliberately separated from the foundation board and left with the project’s maintainers. For an organisation whose procurement process asks “what happens if the vendor loses interest”, that is a meaningfully better answer than single-company stewardship.
Flutter remains a Google project. Google has a documented pattern of discontinuing products, and it also has an unusually strong incentive to keep Flutter healthy, given its own use of it. Treat the risk as real but modest, and note that both frameworks are open source and forkable — the practical risk is not disappearance, it is a slowdown in the pace at which new OS features are supported.
On hiring: React Native draws from the enormous React pool, which makes recruiting faster and quality assessment harder, because web React experience is not mobile experience. Flutter’s Dart requirement narrows the pool but tends to select for people who identify as mobile developers. Native is the smallest and most expensive pool and the only one that can definitively resolve a platform problem in any of the three stacks — which is why cross-platform teams should keep native capability on hand rather than assuming it away.
Platform features that still push you into native code
No cross-platform framework removes the need for native code. It changes how often you need it, and who has to write it.
Flutter handles this through platform channels: named, asynchronous message channels between Dart and Kotlin, Swift, C++ or Objective-C, with handlers that must run on the platform’s main thread. React Native does it through native modules under TurboModules. The mechanics differ; the obligation does not.
Areas where you should assume native work, and budget for it:
- Widgets and complementary surfaces. Home screen widgets, watch apps, Live Activities, App Clips, Android tiles. These are written in the platform’s own toolkit, full stop.
- Background execution. Every platform’s rules here are strict, different, and change between OS versions. Plugins abstract the happy path and leak everywhere else.
- Deep hardware access. Advanced camera control, Bluetooth Low Energy, NFC, biometric edge cases.
- Health, payments and regulated APIs. These usually mean going straight to the platform SDK, and the compliance review will ask about the code that touches them.
- New OS features on day one. Native apps get them at announcement; cross-platform apps get them when a plugin catches up, unless you write the binding yourself.
The practical rule: list every device capability your app needs before choosing a framework, then check whether a maintained plugin exists for each on both platforms. A plugin with no commits since two OS releases ago is a native task in disguise.
App size, cold start and the metrics the stores actually police
This is the section that most often changes a decision, because it is where the platform holders impose limits that no architectural argument can override.
Startup. Google Play measures your app and flags cold startup of five seconds or more, warm startup of two seconds or more, and hot startup of 1.5 seconds or more as excessive. Both cross-platform frameworks carry a fixed initialisation cost that native does not — the Flutter engine, or the JavaScript engine plus bundle. On modern hardware this is comfortably inside the threshold; on the low-end Android devices that make up a large share of installs in many markets, it is a real budget line you have to design around rather than discover.
Stability, which the store enforces. Android vitals sets bad-behaviour thresholds and acts on them: a user-perceived crash rate at or above 1.09% of daily users across all devices, or 8% on a single device model, and a user-perceived ANR rate at or above 0.47% overall or 8% per device model. Exceed them and Play may reduce your app’s visibility and show a warning on your store listing, generally assessed on the last 28 days. Framework choice affects this indirectly — an unmaintained plugin crashing on one manufacturer’s devices is a per-device threshold problem — and it is one of the strongest arguments for keeping your dependency list short whichever way you go.
Size. Flutter’s own guidance is a good model for any framework: measure release builds only, since debug builds are not representative, and use the size analysis tooling and split debug info rather than guessing. Both frameworks add a baseline over an equivalent native binary. Whether that matters depends entirely on your market; in places where users are on constrained data plans it matters a great deal.
The constraint with a date on it. Android requires apps that ship native code to support 16 KB memory page sizes, and from 1 February 2027 Google Play will not accept updates to apps targeting Android 15 or higher that do not. Pure Kotlin and Java apps are unaffected — but every Flutter and React Native app ships native libraries, as does any app with an NDK-based SDK in it, so this lands squarely on cross-platform teams and on their third-party dependencies. Ask any framework or SDK vendor about it now; it is the sort of requirement that quietly strands an app whose dependencies stopped being updated.
When native is the only defensible answer
Not often, but unambiguously in these cases:
- The app is the product and the interface is the differentiator. If a competitor could win on feel alone, do not put an abstraction between you and the platform.
- Sustained real-time media processing. Video effects, audio pipelines, computer vision, AR. You will end up native for the hard parts; better to start there than to arrive by attrition.
- The platform’s newest capabilities are your feature list. If your roadmap is written from WWDC and Google I/O sessions, waiting on plugin support is not viable.
- You are shipping to one platform only. A single-platform app in Flutter or React Native pays the abstraction cost and collects none of the benefit. This is more common than teams admit, especially for internal tools on managed devices.
- A regulator or enterprise client will audit the code. Fewer layers is a simpler conversation.
Everything else is a genuine choice, and for most business applications the cross-platform answer is defensible. If you want the discipline that goes with it, our cross-platform app development work starts by writing down which of these five conditions apply, because if any do, the framework question is already settled — and the project becomes an iOS build, an Android build, or both, scoped as two codebases rather than one.
Sharing code with your web application
The most oversold benefit, and it is worth being precise about what is actually shareable.
React Native shares the React model, the language, and non-visual code: API clients, validation, formatting, state management, business rules. It does not share components, because React Native renders platform views rather than HTML, and your CSS does not travel. Teams who plan for a shared logic package and separate interface layers get a real, durable saving. Teams who expect to reuse their web components get a rewrite with extra steps.
Flutter compiles to web, and the output is best understood as a separate application that happens to run in a browser. It does not produce conventional document markup, which has consequences for search visibility and for anything that expects a normal web page. Use it for an internal tool or an application behind a login; do not use it for your marketing site.
Both frameworks support over-the-air updates for the parts of the app that are not native — Expo’s EAS Update is explicit that JavaScript, styling, copy, translations and images can be shipped this way, while native code, permissions and SDK upgrades require a new binary. This is genuinely useful for fixing a bad string or a broken screen without waiting for review. It is also the feature most likely to get a team into trouble with Apple, because guideline 2.5.2 requires apps to be self-contained and prohibits downloading or executing code that introduces or changes features. Fixing a bug is fine. Shipping a shell and filling it in afterwards is not, and neither is using the channel to add functionality review never saw.
A scoring sheet for your project
Answer these honestly, then look at where the weight sits. There is no total to compute; the value is that disagreements surface as specific rows.
| Question | Points to native | Points to Flutter | Points to React Native |
|---|---|---|---|
| Does the interface need to feel unmistakably like each platform? | Strongly | No | Yes |
| Do you want one identical brand-led interface everywhere? | No | Strongly | Partly |
| Does the app depend on new OS features at launch? | Strongly | No | No |
| Do you already employ React or TypeScript developers? | No | No | Strongly |
| Do you already employ iOS and Android engineers? | Strongly | Neutral | Neutral |
| Is heavy device integration central to the product? | Strongly | Slightly | Slightly |
| Is the app largely lists, forms, navigation and API calls? | Neutral | Strongly | Strongly |
| Is there a web application sharing the same domain logic? | No | Slightly | Strongly |
| Are you shipping to one platform only? | Strongly | No | No |
| Will a small team maintain this for years? | No | Yes | Yes |
| Do low-end Android devices matter commercially? | Yes | Consider start-up cost | Consider start-up cost |
| Do you have native expertise available for the hard parts? | Required | Recommended | Recommended |
Two decision rules that hold up in practice. If three or more rows point strongly to native, stop optimising for a shared codebase — you are going to write native code anyway, and doing it deliberately is cheaper than doing it in fragments. And if your team already has deep expertise in one of these stacks, that usually outweighs every architectural argument above; a team shipping confidently in a framework they know will beat a team learning the theoretically better one.
Whichever way this lands, the work that decides whether the app succeeds happens elsewhere: in the interface design, in the store listing, and in the first ninety seconds of a new user’s experience. We have written separately about what actually moves installs on the App Store and Google Play, and about the checks worth completing before you submit. If you would rather have that argued through with you than settle it in a meeting, talk to us — we will tell you when the answer is native, including when that means we are not the right people to build it.