Overview
Choosing a development approach means deciding which technology to build an iOS and Android app on. It has to be decided at the very start of product development, and it shapes the development structure, hiring, and maintenance cost that follow. This decision is often treated as a side-by-side comparison of options such as Pure Native, Flutter, React Native, and Kotlin Multiplatform. But something has to be settled before any comparison is meaningful: what you intend to share. Once the scope of sharing is fixed, the decision moves down a level. Choosing the scope is a comparison between three categories; once it is fixed, the comparison is between the technologies within that category. If you decide to share the UI implementation, for instance, the next question becomes whether to use Flutter or React Native. Comparing individual technologies without fixing that scope places options with different premises on the same footing. This page explains three categories defined by the scope of sharing, four axes for choosing among them, and the criteria that divide the candidates within each category. It closes with worked examples that walk the decision from the situation of an organization and its product. It does not recommend a particular approach.This area changes quickly. The descriptions on this page reflect the official direction of each technology as of July 2026.
What to share
Development approaches fall into three categories according to what they share.
What the “share the UI implementation” category shares is the code that builds the UI. It does not mean the screens look identical on both platforms. Some approaches assemble each OS’s native UI from a single codebase, and how far the appearance is aligned divides the candidates within the category.
In any of the three categories, moreover, you will write some platform-specific code. A category describes the scope of sharing, not a claim that sharing reaches 100%.
Note that the category attaches not to the name of a technology but to what you choose to do with it. Kotlin Multiplatform, for example, falls under “share logic only” when it keeps native UI, and under “share the UI implementation” when combined with JetBrains’ Compose Multiplatform.
Which category to choose follows from two things: whether the category is available to you at all, and what you take on once you have chosen it.
Choosing a category
Before the axes, check whether any condition narrows the candidates. One example is a product that depends on a vendor SDK offered for native only — payment terminals or contactless card reading — where the vendor’s own support is part of the requirement. An approach that meets such a condition drops out of the candidates regardless of its technical merits. With that settled, four axes drive this decision. The last two are what you take on after choosing. They are not discoveries made afterward, so you check them before you decide.UI and design effort
A constraint on what you can choose. Choosing not to share the UI presupposes enough people to maintain two lines of screen implementation and design.
Team skills and mobility
A constraint on what you can choose. Once the scope of sharing is fixed, the required language is largely fixed too. Where it does not match existing strengths, that gap shows in development speed.
Keeping up with OS changes
Something you take on after choosing. The more layers you depend on, the longer the wait before OS-side changes reach you.
Layer count and investigation
Something you take on after choosing. With a layer in between, you add a step to determine whether a problem originates in your application code or in that layer.
UI and design effort
Choosing not to share the UI presupposes enough people to maintain two lines of screen implementation and design. Where that premise does not hold, Pure Native drops out of the candidates regardless of its technical merits. What matters here is that whether UI implementation and design effort doubles is determined solely by whether you share the UI, independently of whether you share the logic. Sharing business logic with Kotlin Multiplatform, for example, still leaves you implementing screens separately for iOS and Android. The UI implementation effort is the same as Pure Native, and so is the effort of producing two sets of designs. What is removed is the duplication of whatever you put in the shared layer. How far that layer reaches is a design decision: some setups go beyond business logic and pull in the state that drives the screens. Either way the screens themselves are implemented per platform, so UI implementation and design effort is unchanged. That effort includes absorbing the differences between platform UI conventions through design. For example, iOS is said to hold a high share of usage in Japan, and it is common to adopt Pure Native and then bring the Android side closer to the iOS appearance. In that case the team pays the cost of maintaining two lines while the result moves toward a unified UI.Team skills and mobility
Once the scope of sharing is fixed, the required language and framework are largely fixed along with it. Pure Native requires expertise in each platform, while approaches that share the UI implementation center on fluency in a single language and framework. An approach that matches existing strengths keeps learning and hiring costs down, while one that does not shows that gap in development speed. What matters here is not only the size of the hiring market. Mobility within the organization is also a factor: whether existing web frontend developers can contribute to mobile development. For a team that already works in TypeScript and React, React Native is an option that avoids adding a new language ecosystem. That mobility is not unconditional, however. Fluency in TypeScript and React, for example, does not remove the need for platform-specific knowledge such as store submission, certificates and signing, push notifications, and background processing. Sharing removes duplicated implementation, not platform knowledge itself. What these first two axes determine is not which approach is better but which approaches are feasible.Keeping up with OS changes
The more dependency layers you add (frameworks such as Flutter or React Native that carry the sharing), the more waiting is introduced before OS-side changes reach you. This waiting comes in two kinds, and they behave differently. The first is deadline-bound catch-up. Mandatory updates for new OS versions, changes to review requirements, and privacy-related requirements have deadlines set externally. When you have to wait for the dependency layer to respond, the room you have to act on your own shrinks. This is not a question of which features are available, but a risk to your ability to keep shipping. The second is catch-up with a new design. When an OS introduces a new design, whether the dependency layer follows it depends on its design policy. The app keeps working if it does not follow, but the experience gradually drifts away from the OS standard. Pure Native has no such waiting by construction. This property is why Pure Native is chosen for products that face frequent deadline-bound work, or that place OS features at the center of the product. When you do choose to share, this waiting cannot be eliminated, but it can sometimes be worked around case by case by writing platform-specific code. What matters for the decision, therefore, is not whether waiting occurs but how often you will need to write those workarounds. That frequency follows from how much the product depends on OS capabilities.Dependency layers and troubleshooting
More dependency layers mean more steps to isolate a problem. Pure Native lets you investigate directly with each platform’s official tooling, whereas a dependency layer adds a step of determining whether a problem originates in your application code or in that layer. This difference is hard to see in day-to-day development and surfaces during incident investigation and performance work. Investigation takes longer when a problem reproduces on only one platform, or when you have to check whether it matches a known issue in the dependency layer.Judging your category from conditions
Applied to a decision, the four axes map to conditions as follows. These are typical examples rather than an exhaustive set.
It is not unusual for several conditions to point to different categories. When they do, look first for a condition that acts as a cutoff. Where the product depends on new OS features beyond what workarounds can absorb, approaches carrying a dependency layer do not stay in the running at all. If more than one candidate is still standing after that, give priority to the condition that is most expensive to change later. Because a decision about sharing the UI translates directly into volume of implementation, changing it midway carries a larger cost than the others.
The options
Once the category is fixed, the next step is choosing an approach within it. If you chose to share nothing, the approach is settled as Swift and Kotlin, so what this section covers is the two remaining categories.Share the UI implementation
This category contains several approaches. Here we take Flutter and React Native, the pair most often compared, as the example. What separates them is not a technical performance comparison but whether unifying the UI design is a policy of the framework itself. Flutter draws its own controls and states unification across operating systems as a design policy. React Native treats each OS standard as the source of truth, and where unification is needed, you reach it by choosing a UI library that carries it.
Flutter does not use the UI components supplied by the OS; it draws every UI control itself. The official architectural overview cites decoupling from OS dependencies as a benefit of this approach, explaining that the application looks and feels the same even if the OS changes the implementation of its controls. Independence from the OS is positioned as a design goal, not a side effect.
This policy is visible in how the project is run. As of July 2026, Flutter has deferred both support for the new iOS design and support for Android’s Material 3 Expressive. This is stated officially as a deliberate pause to reconsider the architecture rather than a delay.
The important detail is that it applies to both platforms, not just one. Flutter explains the pause as a way to reconsider the architecture for integrating design systems itself, not as a matter of favoring one OS over another.
React Native takes the opposite position, treating each platform’s native UI as the source of truth. The official documentation explains that because React Native components are backed by the same views as Android and iOS, the apps look, feel, and perform like any other apps. When an OS changes its design guidance, that change arrives through updates to the native UI, so catch-up is comparatively fast. In exchange, producing a unified appearance across both platforms means absorbing the platform differences yourself.
Treating each OS standard as the source of truth is a policy of the framework, not a limit on what you can build. Open source UI libraries for aligning the appearance across iOS and Android are readily available, and unifying a React Native design with one of them is common practice. In that case the responsibility for holding the design together moves from the framework to your choice and upkeep of the library.
If you choose React Native, the officially recommended setup is React Native together with Expo. The React Native documentation recommends using a framework and names Expo, and the official blog positions it as the only recommended community framework for new apps. Building without a framework continues to be supported, but it is not the default choice.
Share logic only
Once you have decided to share logic only, Kotlin Multiplatform and Swift on Android are the candidates. Technically the two mirror each other, but for an organization they are asymmetric.
Kotlin Multiplatform uses Kotlin as the shared language, placing the Android language ecosystem at the center. Swift on Android uses Swift, placing the iOS ecosystem at the center. It is a choice of which language ecosystem sits at the center of the organization, and the better answer flips between a team strong in iOS and a team strong in Android.
Their maturity differs. Kotlin Multiplatform was introduced as an experimental feature in 2017, became stable in 2023, and in 2024 Google announced support for it on Android. In that announcement Google stated that its support focuses on sharing business logic, the part most agnostic to user interfaces. Adoption by several large-scale services has also been published.
Swift on Android, by contrast, reached preview in 2025 and its first official SDK was released in March 2026. The official documentation describes embedding Swift code as shared libraries with the UI implemented in Java or Kotlin, which places it explicitly in logic sharing. Its history is short and published production cases are limited. Adopting it means proceeding without prior cases to draw on.
Category boundaries move
Both of the language ecosystems that share logic only have movements toward sharing the UI. The Kotlin ecosystem has Compose Multiplatform and the Swift ecosystem has Skip, both of which extend sharing to the UI. A setup that adopts either falls under “share the UI implementation”.Walking through the decision
This section brings the page together as a single decision flow. The four axes supply the answer at the category branches, and the criteria that separate candidates within a category supply the rest. The three cases below settle on a category and an approach from the situation of an organization and its product. They are typical examples, and the same situation can end elsewhere when another condition takes priority.Able to staff each OS, with OS features at the center
Pure Native fits this case. At a size that can staff each OS, the organization can pay for two lines of implementation and design. Once that is settled, the weight of the decision moves to how the app keeps up with OS changes. The more central the new OS features are, the more a structure that waits on a dependency layer costs, so the choice goes to a setup that has no waiting time to begin with.An existing TypeScript and React team, no dedicated mobile team
React Native fits this case. With no one dedicated to each OS, the implementation has to stay in one line. What matters from there is how readily the existing web frontend people can take part. With a team that works in TypeScript and React, mobile development can start without adding a new language to the organization. And on the premise that design is left to each OS standard, there is no work to design a unified appearance yourself. It is also a choice that takes on leaving the source of truth for design with each OS. Add a unified appearance to the requirements, and choosing and maintaining a UI library to carry it becomes part of the work.OS features central, and a unified appearance you cannot move
Flutter fits this case. A unified appearance points toward sharing the UI implementation and dependence on OS features toward sharing nothing, so the two conditions point to different categories. What gets fixed first is whichever is more expensive to reverse later. The decision about sharing the UI ties directly to the volume of implementation, so it is settled first. What remains is whether to take Flutter or React Native. What separates them here is who keeps the unified appearance in place over time. React Native builds on native views, so when the OS changes its design, that change reaches the app. That is an advantage when the goal is to stay close to each standard, but it works the other way when unification is the requirement: every OS update brings work to absorb the difference on the UI library side. Flutter draws its own controls and is independent of the OS, so its appearance does not change when the OS does. The less a unified appearance can be moved as a requirement, the more this difference weighs. Keeping up with OS features is then taken on as the frequency of writing workarounds. Flutter holds as the conclusion only while that frequency stays within what was assumed. Beyond it, the premise of sharing the UI falls apart. The landing point is then Pure Native, and the unified appearance is approached by paying for two lines and bringing them together through design.Related pages
Choosing a Database
The same shape of decision on the data side — settle the premise first,
then compare products.
Modifiability
Why the price of a structural choice arrives later, as how cheaply the code
can still be changed.
Task Breakdown
Once the approach is fixed, split the build into units that merge and
revert on their own.