Skip to main content
Blog

Mobile App Development: Native vs Cross-Platform in 2024

Last updated Android

Every mobile project begins with the same argument. One person wants native because "it just feels better." Another wants cross-platform because "we can't afford two teams." Both are describing real constraints, and both are usually arguing from a version of the landscape that is several years out of date. The honest answer is that the gap between native and cross-platform has narrowed enormously — and that the right choice depends far more on what your app does than on which framework is fashionable.

This guide walks through the whole decision from first principles: what each approach actually is, what it genuinely costs, where each one breaks, and how to pick without relying on folklore. It assumes no prior mobile experience, and it deliberately avoids declaring a winner, because there isn't one.


What you will learn
  • What "native", "cross-platform" and "hybrid" really mean under the hood
  • How the four serious options compare on performance, cost, hiring and risk
  • A decision framework you can apply to your own app in an afternoon
  • The architecture that survives a change of framework
  • Release, testing and store-review realities nobody mentions in the pitch deck
  • The mistakes that cause expensive rewrites eighteen months in
In this article
  1. The vocabulary, properly defined
  2. How each approach actually works
  3. The four realistic options compared
  4. Performance: what is real and what is folklore
  5. The true cost model
  6. A decision framework
  7. Architecture that survives a stack change
  8. Testing, release and store review
  9. Offline, sync and the hard problems
  10. Eight expensive mistakes
  11. A worked comparison: the same feature in three stacks
  12. Team, process and the parts that are not code
  13. Frequently asked questions

1. The vocabulary, properly defined

The terms get used carelessly, which is where most bad decisions start. Here is what each one actually means.

TermWhat it meansRendering
NativeWritten in the platform's own language and toolkit — Swift/SwiftUI for iOS, Kotlin/Jetpack Compose for AndroidPlatform widgets, drawn by the OS
Cross-platform (bridged)One codebase in a shared language that drives real platform widgetsPlatform widgets, controlled from JavaScript or similar
Cross-platform (self-rendering)One codebase that draws its own UI onto a canvasIts own widgets, pixel-identical everywhere
Shared-logicBusiness logic shared; UI written natively per platformFully native UI
Hybrid / web wrapperA web app inside a native shellA web view
PWAA website with offline support and an install prompt — no store involvedThe browser

Two distinctions matter more than the labels. First: does the framework use the platform's real UI components or draw its own? Second: where does your business logic run — in a native runtime, or in an interpreter shipped inside your app? Almost every practical trade-off follows from those two answers.

2. How each approach actually works

Native

Your code compiles to machine code for the device and calls platform frameworks directly. There is no translation layer, no bridge, and no lag between a new OS feature shipping and you being able to use it. The cost is duplication: two languages, two toolchains, two codebases, two sets of bugs — and, in practice, two teams who solve the same problem slightly differently.

Bridged cross-platform (React Native and relatives)

Your UI is described in JavaScript or TypeScript; a runtime inside the app interprets that description and instructs the platform to create real native views. A button really is a platform button. Modern architectures have largely replaced the old asynchronous message bridge with a synchronous interface layer, which removed the classic stutter that gave this approach its early reputation.

The strength is that you write once, get platform-authentic components, and can drop into native code whenever you need to. The weakness is that the seam between two worlds is real: dependency upgrades touch three ecosystems at once, and hard performance problems require someone who understands both sides.

Self-rendering cross-platform (Flutter)

The framework ships its own rendering engine and paints every pixel itself, the way a game engine does. Your app looks identical on every device because nothing is delegated to the platform. Code is compiled ahead of time to native machine code, so there is no interpreter in the hot path.

The strength is consistency and control — a designer's mockup renders exactly as drawn, everywhere. The weakness is the same thing viewed from another angle: you are responsible for matching platform conventions yourself, accessibility flows through the framework's own bridge, and each OS redesign requires the framework to catch up.

Shared logic, native UI (Kotlin Multiplatform and similar)

Share the parts that are genuinely identical — networking, models, validation, business rules, local storage — and write each UI natively. Typically fifty to seventy percent of code is shared, and the half that users touch is fully native.

This is the most conservative modern option and increasingly the most defensible for teams that already have native expertise. The cost is that you still write two UIs, so it saves less than a single-codebase approach — it just saves it in the places least likely to cause regret.

Web wrapper

A web view in a native shell. Cheap, fast to build, and appropriate for genuinely content-shaped applications. Scrolling, gesture handling and transitions rarely feel right, and store reviewers increasingly reject apps that are a website with an icon. Choose it deliberately for the right shape of app, not as a shortcut.

3. The four realistic options compared

DimensionNativeReact NativeFlutterShared logic + native UI
UI fidelityPerfectVery good — real platform widgetsConsistent, but its own look unless tunedPerfect
Raw performanceBestVery good; JS thread can bottleneckVery good; heavy first paintBest
New OS featuresDay oneWeeks to months, or write a moduleWeeks to months, or write a pluginDay one
Code shared0%85–95%90–98%50–70%
Team shapeTwo specialist teamsOne web-leaning team + native helpOne team + native helpTwo native teams, shared core
Hiring poolDeep but expensiveVery large (web overlap)Growing, more specialisedNarrower
App size floorSmallestModerateLargest (engine bundled)Smallest
Upgrade painLow, twiceHighest — three ecosystemsModerateLow
Best fitHardware, graphics, platform-defining appsContent and transactional apps, web-heavy orgsDesign-led apps wanting one exact lookExisting native teams reducing duplication

4. Performance: what is real and what is folklore

"Cross-platform is slow" was largely true in 2016 and is largely false now — but the ways it can still be slow are specific and worth knowing.

What genuinely differs

  • Cold start. Cross-platform apps carry a runtime or engine that must initialise. The gap is typically a few hundred milliseconds — invisible for most apps, meaningful for one users open thirty times a day.
  • App size. A trivial native app might be a few megabytes; a self-rendering framework adds its engine to every build. On slow connections and cheap devices this affects install conversion measurably.
  • Sustained heavy work. Video processing, on-device inference and complex real-time graphics still favour native, or at minimum a native module doing the heavy lifting.
  • Long list scrolling. The classic weak point. Modern frameworks handle it well when used correctly and badly when list items are rebuilt carelessly — which is a code-quality problem more than a framework problem.

What almost never differs

Network calls, JSON parsing, database reads, form handling and navigation are dominated by I/O and by your own code, not by the framework. For the overwhelming majority of business apps, users cannot tell which stack you used — and the ones who complain about "feel" are usually reacting to animation timing and gesture handling, both of which are fixable in any stack by someone who cares.

The uncomfortable truth: most slow mobile apps are slow because they make too many network calls, ship unoptimised images and block the main thread with work that belongs in the background. Switching frameworks fixes none of that.

5. The true cost model

The pitch for cross-platform is "half the cost." The reality is more like sixty to seventy-five percent of the two-native cost, and the savings are unevenly distributed.

Cost areaNative (two platforms)Single-codebase cross-platform
Initial build2 x1.2–1.4 x
Feature work2 x1.1–1.3 x
Platform-specific workIncludedExtra — modules and workarounds
OS upgrade seasonTwo moderate effortsWaiting, then one larger effort
Dependency upgradesTwo stable ecosystemsOne volatile ecosystem on top of two
Hiring and ramp-upTwo specialist searchesOne search, larger pool
Debugging hard problemsOne layer deepTwo layers deep, needs rarer skills

The costs people forget are all in the second half of that table. Cross-platform frameworks move quickly; a project that skips upgrades for a year faces a painful catch-up, and one that skips two years faces a rewrite. Budget for continuous maintenance, not just for features. This is often the single largest hidden cost in the entire decision.

6. A decision framework

Answer these in order and stop at the first strong signal.

Question 1 — Is the app defined by hardware or platform capability?

Camera pipelines, Bluetooth peripherals, background location, health data, widgets, watch apps, on-device machine learning, AR. If two or more of these are core to what your product is, choose native or shared-logic-with-native-UI. You will spend more time bridging than building otherwise.

Question 2 — What does your existing team already know?

A team of strong web engineers will produce a better React Native app in six months than a better native app, because competence beats theoretical purity. A team of experienced iOS and Android engineers should not be handed a framework that makes their expertise irrelevant; give them shared logic instead.

Question 3 — How strict is the design?

If the brief is "identical on every platform, exactly as drawn", a self-rendering framework gives you that for free and everything else fights you. If the brief is "feels like it belongs on the device", real platform widgets — native or bridged — are the shorter path.

Question 4 — What is your update cadence?

Teams shipping weekly benefit enormously from one codebase and one release checklist. Teams shipping quarterly gain much less, because the coordination overhead of two native codebases is amortised over a longer cycle.

Question 5 — What is the app's expected lifespan?

A two-year campaign app should optimise for speed of delivery. A ten-year platform that will outlive several framework generations should optimise for stability, and native ages more gracefully than any framework.

The shortcut version
  • Hardware-heavy, long-lived, native team → Native, or shared logic with native UI
  • Content or transactional, web-heavy org, fast cadence → React Native
  • Design-led, brand-consistent, small team, one exact look → Flutter
  • Mostly reading content, no offline need, no store requirement → Progressive web app

7. Architecture that survives a stack change

The best insurance against choosing wrong is an architecture where the choice is not load-bearing. Three habits do most of the work.

Keep the UI layer thin

Screens should render state and emit events. Nothing else. No network calls, no business rules, no formatting decisions embedded in view code. When the UI layer is thin, replacing it — even wholesale — is a bounded project rather than a rewrite.

Isolate platform capability behind interfaces

Define your own narrow interface for each platform capability: storage, notifications, camera, biometric authentication, analytics. Application code depends on your interface; the implementation is swappable.

In practice this means defining a small interface of your own — read a value, write a value, delete a value — and having every screen and service depend on that, never on a vendor library directly. One implementation exists per platform or per vendor, and swapping vendors becomes a single-file change rather than a search across the codebase. The same interface makes testing straightforward: tests substitute an in-memory version and run without a device at all.

Own your data layer

A single module owns fetching, caching, offline persistence and conflict resolution, and exposes plain domain objects. This is where most of the genuinely hard logic lives, and it is the part that ports between stacks with the least friction — which is precisely why shared-logic approaches concentrate on it.

8. Testing, release and store review

The testing pyramid, mobile edition

  • Unit tests on business logic and view models. Fast, run on every commit, no device needed. This should be the bulk of your suite.
  • Component tests that render a screen with fake dependencies and assert on what appears. High value per line.
  • End-to-end tests on real devices, covering only the journeys that must never break: sign-in, purchase, the core action. Keep this set small — device tests are slow and inherently flaky.
  • Manual exploratory testing on the cheapest device you officially support, on a throttled connection. This finds more real problems than any automated suite.

Release mechanics

Mobile releases are irreversible in a way web releases are not: once a version is installed, you cannot recall it, and a meaningful share of users will stay on it for months. Three consequences follow.

First, use staged rollouts — both stores let you release to a small percentage and halt if crash rates rise. Second, put a remote configuration and feature-flag system in place before your first release, so a bad feature can be disabled without a new build. Third, treat backward compatibility as permanent: your API must keep serving the version you shipped two years ago, because someone is still running it.

Store review realities

Budget one to three days for review, more for a first submission. The rejections that surprise teams are consistent: apps that are thin wrappers around a website, payment flows that bypass in-app purchase for digital goods, permission requests without a clear justification string, account creation without a deletion path, and missing privacy declarations. Read the current guidelines for both stores before designing anything involving payments or accounts — retrofitting compliance is far more expensive than designing for it.

9. Offline, sync and the hard problems

The hardest part of mobile is rarely the UI. It is that the device is sometimes offline, sometimes on a train, and always the second source of truth.

Decide the offline posture early

There are three honest positions, and choosing late is expensive:

  • Online-only. Show a clear offline state and stop. Simple, and correct for many business apps.
  • Read-offline. Cache fetched data and serve it when disconnected. Moderate complexity, large perceived-quality gain.
  • Full offline. Queue writes locally and reconcile later. Genuinely difficult — you have signed up for conflict resolution, ordering guarantees and eventual consistency.

If you queue writes, define conflict rules explicitly

Two devices edit the same record; one was offline for three days. Last-write-wins is simple and silently destroys data. Field-level merging is better and more work. Explicit user-facing conflict resolution is best where the data matters, and worst for user experience. There is no default answer, which is why the decision must be made deliberately per data type rather than once for the whole app.

Practical rules that avoid most pain

  • Give every locally created record a client-generated identifier so retries are idempotent.
  • Never assume a request that timed out did not succeed on the server.
  • Store a version or timestamp on every syncable record from day one.
  • Make the sync queue inspectable in a debug screen — you will need it.

10. Eight expensive mistakes

  1. Choosing by framework popularity rather than app shape. The question is what your app does, not what is trending.
  2. Assuming cross-platform means no native knowledge needed. Every serious cross-platform project eventually needs someone who can read a native stack trace and write a platform module.
  3. Deferring the offline decision. Retrofitting sync into an online-only app is close to a rewrite of the data layer.
  4. Skipping remote config and feature flags. Without them, every mistake requires a new build and a review cycle.
  5. Testing only on flagship devices. Your median user has a three-year-old mid-range phone on a poor connection.
  6. Letting dependencies drift. Six months of skipped upgrades in a fast-moving ecosystem turns a routine bump into a migration project.
  7. Business logic in view code. Guarantees that any future UI change becomes a rewrite, whatever the stack.
  8. Ignoring accessibility until launch. Labels, contrast, focus order and dynamic type are cheap during development and expensive afterwards — and in many markets they are a legal requirement.

11. A worked comparison: the same feature in three stacks

Abstract comparisons are easy to argue with, so consider a concrete feature that most apps eventually need: a product list that loads from an API, caches for offline reading, supports pull-to-refresh, and opens a detail screen with a shared-element transition.

In native, you write it twice. Each version is roughly two hundred lines using the platform's list component, its own caching library and its built-in transition support. Both feel perfect because both use the components the operating system itself uses. The transition is a few lines on each platform because it is a first-class platform feature. The cost is that a change to the caching rule has to be made, reviewed and tested twice, and the two implementations will drift subtly over time no matter how disciplined the team is.

In a bridged framework, you write it once. The list is a real platform list underneath, so scrolling feels correct without effort. Caching is one implementation shared by both platforms, which is a genuine saving in both code and in the number of bugs. The shared-element transition is where you meet the seam: it is supported, but the animation runs across the boundary between your code and the platform, and getting it smooth on a mid-range Android device may take a day of investigation that a native developer would not have spent.

In a self-rendering framework, you also write it once, and the transition is straightforward because the framework owns every pixel and can animate freely between screens. The list scrolls well provided you build rows efficiently. What you give up is automatic platform behaviour: the overscroll effect, the exact scroll physics and the context-menu conventions differ per platform, and matching them is your job rather than the toolkit's. For a strongly branded app that is irrelevant; for an app trying to feel invisible on the device it is real work.

The pattern generalises. Shared logic saves real money and real defects. Shared presentation saves money too, but converts some platform-provided behaviour into work you must do deliberately. That is the trade in one sentence, and it is why the decision hinges on how much your product depends on feeling native versus feeling identical.

12. Team, process and the parts that are not code

Mobile projects fail on process at least as often as on technology, and the process problems are consistent enough to be worth naming.

Design must account for two platforms even when the code does not. Navigation patterns differ, back behaviour differs, date pickers differ, and share sheets differ. A design that specifies one behaviour forces the team to either violate a platform convention or improvise. Agree early which conventions you will follow per platform and which you will deliberately override for brand reasons.

Device coverage needs a policy, not a preference. Decide the oldest OS version and the minimum device class you support, publish it, and revisit it quarterly using your own analytics rather than industry averages. Supporting one extra old version can add a surprising amount of conditional code; dropping it too aggressively can cut off a meaningful slice of paying users. Only your own data answers that.

Crash reporting and analytics are prerequisites, not enhancements. Without a crash reporter wired in before the first release, you learn about problems from store reviews, which is both slow and public. Without event analytics on the core journey, you cannot tell whether a redesign helped. Both take under a day to add at the start and are painful to backfill later.

Plan the app store presence as part of the project. Screenshots, description, keywords, privacy declarations and a support URL are all required, and the first submission always takes longer than expected. Store listing quality also affects install conversion measurably, which makes it engineering-adjacent work worth doing properly rather than a marketing afterthought.

Decide how you will talk to users on old versions. A remotely controlled message with a minimum-version gate is the simplest safety valve you can build, and it converts "we shipped a bad build" from a crisis into an inconvenience.

13. Frequently asked questions

Which is genuinely faster to build with?

For the first version of a standard business app — lists, forms, authentication, payments — a single-codebase cross-platform approach is typically thirty to forty percent faster than two native builds. That advantage shrinks as platform-specific requirements accumulate, and it can reverse entirely for hardware-centric apps where you end up writing native modules for everything that matters.

Can users tell the difference?

Rarely, and not in the way people assume. Users notice slow startup, janky scrolling, gestures that do not behave like the rest of the phone, and text that ignores their accessibility settings. All four are achievable in every stack and avoidable in every stack. Nobody has ever uninstalled an app because of its rendering architecture.

Is React Native still a serious choice?

Yes. Its modern architecture removed the asynchronous bridge that caused its early performance reputation, and it remains the strongest option for organisations whose engineering strength is in web technology. Its main ongoing cost is upgrade discipline: it sits on top of two platform ecosystems plus its own, and neglect compounds faster than in native projects.

When is Flutter the clear answer?

When the design must be identical everywhere and is not trying to imitate platform conventions; when the team is small and would rather learn one system deeply than two shallowly; and when you value ahead-of-time compilation with no interpreter in the hot path. It is less compelling when you need deep, unusual platform integration, or when the product must feel native above all else.

Should we consider a progressive web app instead?

If your app is mainly content and transactions, needs no significant offline capability and no hardware access, a PWA removes store review, store fees and installation friction entirely, and updates instantly. The trade-offs are real: no store presence, weaker push and background capability, and platform-dependent support for installation. It is an excellent complement to a native app and a poor substitute for one when engagement depends on being on the home screen.

Can we mix approaches?

Yes, and mature apps often do. Adding a cross-platform screen inside a native app, or a native module inside a cross-platform app, is a supported pattern. The cost is two build systems, two debugging stories and a navigation seam that needs care. Adopt it deliberately for a specific reason — a heavy screen that needs native performance, or a new feature a smaller team can own — rather than drifting into it.

How do we handle the two-year-old version still in the wild?

Version your API from the first release and never break a shipped contract. Include a remotely controlled minimum-supported-version check that can show a blocking upgrade prompt, and test that path before you need it. Instrument version distribution so you know what you are still serving; deprecation without that data is guesswork.

What does a realistic first release look like?

One platform, one core journey done properly, crash reporting, analytics on that journey, remote config, staged rollout, and a tested path to disable anything risky. Shipping to both stores simultaneously doubles your review exposure and your incident surface at exactly the moment you know least about how the app behaves in the wild.

Key takeaways

  • App shape decides the stack. Hardware-centric products lean native; content and transactional products lean cross-platform.
  • Team skill beats theoretical purity. The best stack is the one your team can maintain well for years.
  • Performance folklore is outdated. Real differences are cold start, app size and sustained heavy work — not everyday screens.
  • Savings are smaller than advertised. Expect around sixty to seventy-five percent of two-native cost, and budget for continuous framework upgrades.
  • Architecture is your insurance. Thin UI, capabilities behind interfaces, and an owned data layer make the framework a replaceable detail.
  • Releases are irreversible. Feature flags, remote config and staged rollouts are prerequisites, not refinements.

Pick the option that your team can maintain for the next five years, structure the code so the choice is reversible, and then spend the argument budget you saved on the things users actually feel: startup time, scrolling, offline behaviour and getting the core journey right.

Enjoyed this article?

Get more engineering insights from ELIVTECH — or talk to us about your project.

Get in touch