Every cross-platform comparison you have read opens by insisting there is no wrong answer. That is a polite way of avoiding the question. There are wrong answers — they just depend on what you are building and who is building it.
So here is the version with opinions in it.
The three are not solving the same problem
This is the part most comparisons skip, and it explains almost every disagreement about them.
Flutter renders its own UI. It ships a rendering engine, draws every pixel itself, and does not use the platform's native widgets at all. A Flutter button is a Flutter button on both platforms, which is why a Flutter app looks identical everywhere and why it can look subtly foreign on both.
React Native does the opposite. It uses actual native components — a real UIButton on iOS, a real Android button — driven from JavaScript. You get platform-native look and behaviour for free, and you inherit the complexity of a bridge between two runtimes.
Kotlin Multiplatform shares neither. It shares your business logic — networking, data models, validation, storage — and leaves the entire UI native on each platform. You write SwiftUI on iOS and Jetpack Compose on Android, and they call into one shared Kotlin core.
One of these is not like the others. Flutter and React Native are trying to write the app once. KMP is explicitly not.

Fig. — Three different answers to the question of what "shared" should mean.
Flutter: the one that ships fastest
If your priority is getting a polished, consistent app onto both stores quickly, Flutter is still the strongest answer, and it is not close.
The reason is that owning the rendering layer eliminates an entire category of problem. Your layout behaves identically on both platforms because it is the same code drawing the same pixels. You are not chasing a bug that only appears on one Android OEM's font scaling. Hot reload genuinely works. The widget library is comprehensive enough that most screens need no platform-specific code at all.
The costs are real though. App size starts higher because you are shipping an engine. Deep platform integration — a complex camera pipeline, a Bluetooth peripheral, anything unusual — means writing platform channels, which is native code with extra steps and a boundary to debug across. And when the OS ships a new native control, Flutter reimplements it on its own schedule rather than inheriting it.
The accessibility story deserves a mention too. Because Flutter draws rather than composes native views, screen reader support depends on Flutter's semantics layer correctly describing what it painted. It is decent now. It is still a translation, and translations lose things.
Pick Flutter for consumer apps with custom design systems, content-heavy products, and anything where a distinctive brand look matters more than platform convention.
React Native: the one that fits the team you already have
The strongest argument for React Native has never been technical. It is that you probably already employ React developers, and they can be productive in it in a week.
Do not underestimate that. A team of four web engineers shipping a competent mobile app without hiring is a materially different proposition from recruiting Swift and Kotlin specialists in a market where both are expensive.
The technical picture has also improved more than its reputation suggests. The new architecture removed the asynchronous bridge that caused most of the historic performance complaints, and the gap between a well-built React Native app and a native one is now invisible to users for the large majority of apps.
Where it still hurts: dependency management. A React Native project of any age accumulates native modules from different maintainers on different upgrade schedules, and a major version bump can turn into a week of untangling. That maintenance tax is the honest downside, and it is ongoing rather than one-off.
Pick React Native when your team is already JavaScript-heavy, when you want genuine platform-native feel without writing two UIs, or when you need to share logic with an existing web app.
Kotlin Multiplatform: the one that admits UI is the hard part
KMP is the most interesting of the three because it refuses the premise. It does not try to write your UI once. It assumes you will build both UIs natively, and it shares only the layer underneath.
That sounds like more work, and initially it is. What you get in exchange is that the hardest 40% of your app — the networking, the offline sync, the data models, the business rules, the validation logic — exists once, in one language, tested once. Meanwhile each platform's UI is fully native, uses the current OS controls the day they ship, and behaves exactly the way users of that platform expect.
For a team that already has iOS and Android developers, this is often the highest-quality outcome available. Nobody has to learn a new UI framework. The apps feel right. The duplicated logic that used to drift between platforms stops drifting.
The caveats are honest ones. You still need both skill sets, so it saves less headcount than the others. The tooling around iOS integration has improved a lot but remains rougher than Android's. And the ecosystem is smaller, so you write more yourself.
Pick KMP when you already have native developers, when correctness of shared logic matters more than shipping speed, or when the app will live for years and you are optimising for maintenance rather than launch.
What about performance, actually
Performance is the argument people reach for when they want a technical reason for a preference they already hold, so it is worth being precise about where it does and does not matter.
For the overwhelming majority of apps — forms, lists, content, checkout, chat — all three are fast enough that users cannot tell. The bottleneck in a typical app is the network, not the rendering layer. Arguing about frame times while your API takes 900 milliseconds to return a product list is optimising the wrong thing.
Where the differences become visible: long scrolling lists with complex cells, animation-heavy interfaces running at 120Hz, anything doing real-time image or video processing, and apps that must launch instantly on low-end Android hardware. Flutter and KMP have an edge on the first three. On cold start and app size, native and KMP win, and Flutter's engine is a genuine handicap on cheap devices in markets where storage is tight.
The useful test is not a benchmark. Build your single most complex screen in whichever framework you are considering, run it on the worst phone your users actually own, and see how it feels. That takes two days and settles the argument better than any comparison table.
The cost nobody puts in the estimate
Hiring is where cross-platform economics either work or quietly fail.
Flutter developers are plentiful and cheaper than native specialists, but the pool is shallower on deep expertise — plenty of people can build screens, fewer can debug a platform channel or profile a jank problem in the engine. React Native inherits the enormous React talent pool, which is its single biggest practical advantage, though "knows React" and "has shipped and maintained a React Native app through three OS releases" are different things and priced differently.
KMP is the hardest to hire for, because you are not hiring KMP developers. You are hiring iOS and Android developers who are willing to work in shared Kotlin, and the second half of that sentence eliminates more candidates than you would expect.
Factor in the replacement cost too. If one person leaves, how long until someone else can safely change the code? That question favours the boring choice more often than teams admit when they are choosing.
The decision, without hedging
If you have no mobile team and need to ship this quarter, choose Flutter. Shipping speed and consistency dominate everything else at that stage, and you can rewrite later if the product survives.
If you have a React team and no mobile hires planned, choose React Native. The team fit outweighs the maintenance tax, and the performance objection no longer holds for most products.
If you have iOS and Android engineers already, choose Kotlin Multiplatform. You are not saving people anyway, so spend the shared code where it actually pays — the logic, not the buttons.
There is a fourth option that deserves saying out loud: if you are building one app for one platform because that is where your users actually are, build it natively and stop reading comparison articles. Cross-platform is a solution to the problem of supporting two platforms. If you do not have that problem, you are paying a tax for nothing — an extra abstraction layer, an extra upgrade treadmill, and a slower path to any new OS feature. Plenty of teams choose Flutter for an app that only ever ships on Android.
And if your app is genuinely simple, a well-built mobile web app might close the gap entirely. That is not the fashionable answer, but plenty of apps in the stores would have been better as a website with a decent install prompt.
One caveat on all of the above: this is a decision you can revisit, and teams treat it as though it were permanent. Rewriting a two-year-old app in a different framework is expensive but survivable, and it is a smaller project than most people assume because the hard-won parts — the API contracts, the business rules, the understanding of what users actually do — carry over unchanged. What does not carry over is the UI layer, which is usually the part that needed rewriting anyway. Choosing wrong is recoverable. Spending four months choosing is not.
The framework choice matters less than teams expect, and the team you have matters far more than they want to admit. A capable team ships a good app in the wrong framework. A struggling team ships a bad one in the right framework, on schedule, and nobody can work out why it feels cheap.
Picking the right stack for a specific team and product is the sort of work we do at CODT.


