Pavan Rangani

HomeBlogKotlin Multiplatform vs Flutter vs React Native 2026

Kotlin Multiplatform vs Flutter vs React Native 2026

By Pavan Rangani · March 9, 2026 · Mobile Development

Kotlin Multiplatform vs Flutter vs React Native 2026

Kotlin Multiplatform vs Flutter vs React Native: The Honest Comparison

Every cross-platform comparison article picks a winner. This one won’t, because Kotlin Multiplatform vs Flutter vs React Native isn’t about which framework is “best” — it’s about which one is right for your specific team, project, and constraints. Therefore, this guide gives you the honest trade-offs, real performance data, and a decision framework based on factors that actually matter in production. Moreover, it focuses on the architectural consequences of each approach, because those consequences follow you for the entire life of the codebase.

The Fundamental Difference: How Each Framework Works

Understanding the architectural approach of each framework matters more than benchmarks because it determines everything else — debugging experience, native API access, hiring difficulty, and long-term maintenance cost.

Kotlin Multiplatform (KMP) shares business logic (networking, data processing, state management) across platforms but uses native UI toolkits for rendering. On Android, your UI is Jetpack Compose. On iOS, it’s SwiftUI. The shared code compiles to JVM bytecode for Android and native binary for iOS (via Kotlin/Native). Consequently, your app’s UI is genuinely native — not a simulation of native.

Flutter takes the opposite approach. Everything — UI, logic, navigation — runs through Flutter’s own rendering engine (Impeller, replacing Skia). It draws every pixel itself using the device GPU, without using platform UI components. This means your app looks identical on Android and iOS, but it also means it looks like a Flutter app, not a native Android or iOS app.

React Native sits in the middle. Your JavaScript code calls into native components through a bridge (now replaced by the faster JSI architecture). When you render a <Text> component, React Native creates a real UILabel on iOS and a real TextView on Android. The new architecture (Fabric renderer + TurboModules) significantly reduced the overhead of this bridging.

// Kotlin Multiplatform: Shared business logic
// commonMain/src/data/OrderRepository.kt
class OrderRepository(private val api: OrderApi, private val db: OrderDatabase) {

    suspend fun getOrders(): Result<List<Order>> = runCatching {
        // Try local cache first
        val cached = db.getOrders()
        if (cached.isNotEmpty() && !cached.isStale()) return@runCatching cached

        // Fetch from API and cache
        val orders = api.fetchOrders()
        db.saveOrders(orders)
        orders
    }

    suspend fun placeOrder(items: List<CartItem>): Result<Order> = runCatching {
        val order = api.createOrder(CreateOrderRequest(items))
        db.saveOrder(order)
        analyticsTracker.trackPurchase(order)  // Also shared code
        order
    }
}

// Android UI: Native Jetpack Compose (NOT shared)
@Composable
fun OrderListScreen(viewModel: OrderViewModel = koinViewModel()) {
    val orders by viewModel.orders.collectAsState()
    LazyColumn {
        items(orders) { order ->
            OrderCard(order) // Pure Compose, follows Material Design 3
        }
    }
}

// iOS UI: Native SwiftUI (NOT shared)
struct OrderListView: View {
    @StateObject var viewModel = OrderViewModel()
    var body: some View {
        List(viewModel.orders) { order in
            OrderRow(order: order) // Pure SwiftUI, follows HIG
        }
    }
}
Kotlin Multiplatform vs Flutter mobile development
KMP shares business logic while keeping UI native — Flutter renders everything through its own engine

Performance: The Real Numbers

Marketing pages claim every framework is “near-native performance.” Here’s what published benchmarks and vendor docs generally show:

Startup time (cold launch to interactive):

  • KMP with native UI: 300-500ms (identical to fully native)
  • Flutter: 400-700ms (Impeller improved this significantly from Skia)
  • React Native (new arch): 500-900ms (JSI eliminated the old bridge bottleneck)

List scrolling (1000 items with images):

  • KMP: 60fps consistent (it IS native scrolling)
  • Flutter: 58-60fps with Impeller (occasional frame drops during image loading)
  • React Native: 55-60fps (improved dramatically with Fabric renderer)

App size (minimal app with networking):

  • KMP: ~8MB Android, ~12MB iOS (shared library adds ~2-3MB)
  • Flutter: ~15MB Android, ~30MB iOS (includes Impeller engine)
  • React Native: ~12MB Android, ~20MB iOS (includes Hermes engine)

The honest assessment: all three frameworks deliver acceptable performance for 95% of applications. The differences only matter for graphics-intensive apps, real-time audio/video processing, or devices with very limited resources. However, KMP has an inherent advantage because the UI is native — there’s zero rendering overhead. In contrast, Flutter ships its own engine, which is why its baseline binary is larger and why its first frame on a cold start has more work to do.

State Management and Concurrency Across the Boundary

The hardest part of any cross-platform stack is rarely the rendering — it’s how state crosses the boundary between shared and native code. Each framework solves this differently, and those differences shape your day-to-day code far more than fps numbers do.

In KMP, you typically expose state with Kotlin coroutines and StateFlow. On Android this maps directly to collectAsState(). On iOS, however, Swift cannot consume a Kotlin Flow natively, so teams wrap it with a small bridge (often KMP-NativeCoroutines or a hand-rolled observer). The example below shows the pattern that keeps the iOS side idiomatic:

// iOS: bridging a Kotlin StateFlow into SwiftUI
@MainActor
final class OrderViewModel: ObservableObject {
    @Published private(set) var orders: [Order] = []
    private let repo = OrderRepository()
    private var task: Task<Void, Never>?

    func start() {
        task = Task {
            // asyncSequence comes from KMP-NativeCoroutines
            for await list in repo.ordersFlow.asyncSequence() {
                self.orders = list
            }
        }
    }

    func stop() { task?.cancel() }
}

Flutter centralizes state in one language. Whether you pick Riverpod, Bloc, or plain ChangeNotifier, the model lives in Dart and the widget tree rebuilds reactively — no boundary to cross. React Native keeps state in JavaScript (Redux, Zustand, or React context), and the Fabric renderer reconciles it to native views. The practical takeaway is that Flutter and React Native have a single mental model for state, whereas KMP asks you to think about two consumers of the same stream. That extra glue is the price of a fully native UI.

Native API Access and Platform Channels

Sooner or later every app needs something the framework doesn’t ship: Bluetooth, secure enclave, HealthKit, or a vendor SDK. How painful that is differs sharply between the three.

KMP uses the expect/actual mechanism. You declare an expected API in common code and provide a platform-specific implementation. Because the iOS side is real native Swift, you can call any system framework directly without a serialization layer:

// commonMain
expect class BiometricAuth {
    suspend fun authenticate(reason: String): Boolean
}

// androidMain — uses BiometricPrompt
actual class BiometricAuth(private val activity: FragmentActivity) {
    actual suspend fun authenticate(reason: String): Boolean { /* ... */ }
}

// iosMain — uses LocalAuthentication (LAContext) directly
actual class BiometricAuth {
    actual suspend fun authenticate(reason: String): Boolean { /* ... */ }
}

Flutter and React Native both rely on a channel: you write native code on each side and pass messages across (MethodChannel in Flutter, a TurboModule in React Native). This works well, but every argument must be serializable, errors lose their native stack traces at the boundary, and you maintain glue code in three languages for one feature. For apps that lean heavily on platform SDKs, KMP’s direct access is a genuine and underrated advantage.

Kotlin Multiplatform vs Flutter: Developer Experience

KMP’s strength is leveraging existing skills. Android developers already know Kotlin. iOS developers write their UI in SwiftUI as usual. The shared code is “just Kotlin” with no special paradigms to learn. The weakness is that you maintain two separate UI codebases — every screen is implemented twice. Additionally, the iOS toolchain (linking the Kotlin framework into Xcode) is still rougher than the Android side, and build times for Kotlin/Native can be slow.

Flutter’s strength is speed of development. Write once, run everywhere — including web and desktop. Hot reload is excellent, and the widget library is comprehensive. The weakness is that it doesn’t look or feel native on either platform (Material Design on iOS feels wrong to iOS users), and debugging platform-specific issues requires diving into platform channels.

React Native’s strength is the JavaScript ecosystem. npm packages, React patterns, and web developer skills transfer directly. The weakness is the “uncanny valley” — it uses native components but the behavior doesn’t always match native exactly, leading to subtle UX issues that are hard to diagnose. Furthermore, the upgrade path between React Native versions has historically been painful, although tools like the Upgrade Helper and Expo’s managed workflow have eased it considerably.

Mobile development workspace
Developer experience depends heavily on your team’s existing skill set

The Decision Framework — When to Choose Each

Choose Kotlin Multiplatform when:

  • UI quality is non-negotiable — banking apps, consumer apps where polish matters
  • Your team has Android and iOS developers (or can hire both)
  • You need deep platform API access (HealthKit, ARKit, Android automotive)
  • You want to incrementally adopt — KMP can be added to existing native apps module by module

Choose Flutter when:

  • Speed of delivery matters more than native feel — MVPs, internal tools, prototypes
  • You also need web and desktop from the same codebase
  • Visual consistency across platforms is desired (brand-heavy apps)
  • Your team is small and can’t maintain two UI codebases

Choose React Native when:

  • Your team’s primary expertise is JavaScript/TypeScript
  • You have an existing React web application and want to share code
  • You need a large ecosystem of ready-made packages
  • You’re building a content-heavy app (social media, news, e-commerce)
Cross-platform testing devices
Test on real devices — emulators hide performance issues all three frameworks have

When NOT to Use Cross-Platform at All — and the Hidden Trade-offs

The honest counterpoint is that none of these frameworks is free. Each carries trade-offs that rarely appear on a landing page, so weigh them before committing.

First, consider going fully native when your app’s value is the platform experience itself: a heavily animated game, an AR-first product, a wearables companion, or anything that must adopt new OS features on launch day. Cross-platform layers always lag a release or two behind the latest iOS and Android APIs, so if being first matters, the abstraction costs you.

Second, watch the team-shape trade-off. KMP doubles your UI surface, so a team without genuine iOS depth will quietly ship a second-class iOS app. Flutter avoids that but introduces a single point of failure: you are betting on one engine and one ecosystem, and your designers must accept that pixel-perfect platform conventions are negotiable. React Native gives you the npm world, but that world includes abandoned native modules, and a single unmaintained dependency can block an OS upgrade for months.

Third, account for the maintenance tail. Flutter web is still weaker for text-heavy, SEO-driven content; React Native upgrades can consume real sprint time; and KMP’s iOS build integration occasionally breaks with new Xcode releases. A common pattern in mature teams is to start with the framework that matches their existing skills, isolate platform-specific code behind clear interfaces, and revisit the decision only when a concrete limitation — not a hypothetical one — actually blocks them. In short, the framework that your team can debug at 2am is usually the right one.

What Companies Actually Use

KMP is used by Netflix (shared networking layer), VMware, Philips, and Cash App. These are companies where native UI quality is essential and they have the team size to maintain two UI layers.

Flutter is used by Google (Google Pay, Stadia), BMW, Alibaba, and Nubank. These companies prioritize rapid iteration and visual consistency across platforms.

React Native is used by Meta (Facebook, Instagram), Shopify, Microsoft (Teams, Office), and Discord. These companies leverage their massive JavaScript developer base.

Notice that all three frameworks are used by billion-dollar companies in production. The framework choice didn’t determine their success — their execution did. Notably, several of these teams adopted their framework incrementally, wrapping it around an existing native app rather than rewriting from scratch, which is a far lower-risk path than a green-field bet.

Related Reading:

Resources:

In conclusion, Kotlin Multiplatform vs Flutter vs React Native comes down to this: What does your team know? How important is native UI quality? How many platforms do you target? Answer those three questions honestly, weigh the trade-offs each abstraction imposes, and the framework choice becomes obvious.

← Back to all articles