KMP 101: An introduction to the multiplatform paradigm
By Rodrigo Sicarelli 10 min read Updated
We use a wide range of apps every day on phones, watches, TVs, and computers, all part of a broad digital ecosystem.
This diversity of platforms demands development strategies that deliver simultaneous updates and a consistent user experience.
In this article, we’ll explore Kotlin Multiplatform (KMP) and how it compares to other cross-platform technologies like React Native and Flutter. We’ll discuss the strengths and challenges of these approaches, offering useful insights for devs looking for efficient solutions to multiplatform development.
What does building “native” mean?
Native development is building apps made to run on one specific platform—Android, iOS, desktop, web—taking full advantage of all its capabilities.
Native apps integrate seamlessly with the hardware and follow the platform’s design guidelines, which results in responsive interfaces and immediate access to the latest system updates.
For each platform, the vendors provide an SDK (Software Development Kit) that makes it easier to build dedicated applications.
That said, native development comes with its own challenges, such as:
- Having to adapt to different environments and languages
- Managing multiple codebases
- Dealing with device fragmentation, like varying screen sizes and system versions
- Requiring constant attention to new operating system updates
- Backward compatibility to keep things working on older versions
The complexity grows with the need to master platform-specific tools and APIs, which makes maintenance more labor-intensive.

Introducing cross-platform frameworks
Cross-platform frameworks like React Native and Flutter ship their own SDK, which can act as an extra layer on top of the native SDK.
There’s no denying how much this kind of solution has grown across the app ecosystem. Taking Flutter’s data:
- In 2021, Flutter accounted for 3.2% of the total, with more than 150,000 of the 4.67 million apps on the Play Store [1, 2].

- In the third quarter of 2022, Flutter accounted for around 14.1%, with more than 500,000 of the 3.55 million apps published on the Play Store [1, 2].

- In November 2023, Flutter sits at around 35%, with 1 million of the 2.87 million apps available on the Play Store [1, 2].

The demand for cross-platform solutions comes from the desire to simplify the complex process of building apps for multiple platforms.
Having to master different languages and SDKs for each platform—like Kotlin for Android and Swift for iOS—on top of constant technological updates, poses a major long-term challenge.
Cross-platform frameworks like Flutter and React Native offer a more efficient path, letting you use a single codebase across several platforms and saving significant time and effort.
Introducing React Native
React Native is an open-source framework that connects JavaScript and React with native components for Android and iOS.
This approach is especially convenient for devs with a background in the Web/React world.
- A
Textcomponent in React Native is converted into aUITextViewon iOS. - On Android, that same
Textcomponent becomes aTextView.
Today, React Native has two types of architecture: the current one and the new one.
React Native’s current architecture
React Native’s current, stable architecture is based on three main threads:
- JavaScript thread: Responsible for running the JavaScript code.
- Native main thread: Or “main thread,” it manages the UI and user interactions.
- Background thread (Shadow Node): Handles creating and manipulating the nodes.
Communication between JavaScript and native code happens over a “bridge,” which works like a data-transmission terminal, allowing the required operations to be deserialized and executed.

Challenges of the old architecture
- Asynchronicity: the bridge operates asynchronously, which can cause UI update delays even when no wait is necessary.
- Single-threaded JavaScript execution: it confines all computation to a single thread, which can cause blocking and delays on intensive operations.
- Serialization overhead: transferring data between layers requires serialization (usually as JSON) and deserialization, adding computational overhead and hurting performance.
React Native’s new architecture
React Native’s new architecture focuses on improving communication between threads, removing the need for serialization/deserialization and using multiple threads to boost performance.
⚠️ Experimental phase
This new architecture is still experimental and subject to change as the project evolves.
It’s important to be aware that the current implementation involves several manual steps and doesn’t reflect the final development experience envisioned for the revamped architecture.
Key components of the new architecture
- Fabric: A complete rewrite of the rendering layer, optimizing the interaction between JavaScript and native code. Fabric removes the need for serialization and deserialization, enabling immediate UI updates and smoother animations while reducing the overall computational load.
- JSI (JavaScript Interface, a JavaScript interface to native code): Replaces the traditional bridge, offering a lighter abstraction layer that allows synchronous calls between JavaScript and native code.
- TurboModules: Optimized modules that use JSI for more direct and efficient access.
- React Renderer: A new renderer that works with JSI to improve UI performance.

Turbo Modules
Previously, communication in React Native between the native and JavaScript layers happened through the JavaScript bridge, or the “Native Modules.”
Turbo Modules represent a significant evolution of NativeModule in React Native, tackling challenges like premature initialization and data serialization.
- Lazy loading: they enable lazy loading of modules, speeding up app startup.
- Direct communication: by avoiding the JavaScript Bridge and communicating directly with native code, they reduce the communication overhead between JavaScript and native code.
- Codegen for type safety: Codegen generates a JavaScript interface at build time, ensuring the native code stays in sync with the data coming from the JavaScript layer.
- Use of JSI: the JSI (JavaScript Interface) bindings enable efficient, direct interaction between JavaScript and native code without the bridge, providing faster and more optimized communication.
Fabric leverages the capabilities of Turbo Modules and Codegen. Together, these components form the pillars of React Native’s new architecture, delivering improved performance and more efficient interoperability between native code and JavaScript.
🔗 Exploring React Native’s new architecture
Introducing Flutter
Flutter is an open-source user-interface development kit (UI toolkit and framework), created by Google in 2015 and based on the Dart programming language, that makes it possible to build natively compiled apps for the Android, iOS, Windows, Mac, Linux, Fuchsia, and Web operating systems.
Architecturally, Flutter has three layers—the framework, the engine, and the platform—and it relies on Dart-specific features like ahead-of-time (AOT) compilation.
As a dev, you mainly interact with the framework, writing the app and the widgets (Flutter’s UI components) declaratively using Dart.
The engine then renders that to a canvas using Skia, which is later sent to the native platforms: Android, iOS, or web. The native platform presents the canvas and sends the events that occur back:

Flutter vs React Native
Moving from React Native to Flutter reveals an evolution in cross-platform development. While React Native offers an efficient path with JavaScript, Flutter stands out with the flexibility of Dart, a language optimized for interactive UIs.
Although Flutter’s architecture is similar to React Native’s, there’s a significant difference in terms of performance.
One of the key components that lets Flutter achieve better performance than React Native is its deeper integration with the native side, which means it doesn’t use the traditional SDKs.
Instead, Flutter uses the Android NDK and iOS’s LLVM to compile the C/C++ code that comes from the engine.
With React Native’s new architecture, this performance gap may become less pronounced.
Flutter’s downsides
While Flutter delivers solid performance, beating React Native when it comes to compiling Dart to native code, it faces challenges like increased app size, due to bundling its runtime engine and widgets.
On top of that, extending Flutter with functionality it doesn’t support natively requires communication between Dart and the native languages through specific channels and data structures, which can be a less efficient and more complex solution compared to the interoperability between Java and Kotlin or Objective-C and Swift.

🔗 Android & iOS native vs. Flutter vs. Compose Multiplatform
The Dart challenge in Flutter
Like any language, Dart comes with its own natural learning and adoption curve.
While Dart is a modern, dynamic language, it’s common for devs from other native platforms to hit a barrier when stepping into this new ecosystem, missing language-specific features from the likes of Kotlin or Swift.
Dart is constantly improving and, while it may not feel as mature as established languages, it offers a range of interesting features that are gaining recognition in the development community.
Final thoughts on cross-platform
Cross-platform solutions abstract away the native complexities, letting you write a single codebase for many devices.
But it’s common to run into limitations when integrating with the native platform, impacting the app’s performance and experience.
On top of that, adapting to platform updates can be slow, since the cross-platform framework needs to be updated to support new native features.
Introducing Kotlin Multiplatform (KMP)
KMP stands out in how it integrates with native platforms. This approach lets devs share business logic while keeping native interfaces, offering an ideal balance between efficiency and customization.
Instead of trying to fully abstract away the native platform, KMP empowers native devs with open-source machinery that handles compiling applications for Android, iOS, Web, macOS, Windows, Linux, and more.

KMP aims to:
- Keep developing platform-specific features as natural and close to native development as possible.
- Ensure native developers don’t run into trouble when working with the shared code.
- Make interoperability between native and shared code easy, so interacting with the shared code feels intuitive and familiar.
Flexibility and native UI
With KMP, the versions of your app can have a lot in common, but also differ significantly, especially when it comes to UIs.
KMP doesn’t impose limits on how you build your app’s UI. You can use any style and framework you want, including the most modern ones, like Jetpack Compose for Android and SwiftUI for iOS. This lets you use platform-specific elements, delivering a native UI experience to your users.

Sharing Kotlin code with the platforms
Given KMP’s flexible spirit, there’s currently a range of strategies devs can adopt to use KMP.
Some common approaches:
- Sharing domain models: Using common classes like entities, DTOs (Data Transfer Objects), server responses, etc., that stay consistent across all platforms.
- Infrastructure components: Sharing logic related to networking, data persistence, cache handling, and so on.
- Experimentation and analytics: Code that enables in-app experimentation, like defining feature flags, analytics events, etc.
- Business logic: Code that defines business rules, validations, and algorithms essential to how the application works.
- Utilities: Helper functions and classes that can be used in different parts of the application, like string manipulation, date formatting, constants, etc.
- Tests: Writing unit and integration tests that can run on all platforms, ensuring the consistency and reliability of the shared code.
- Hardware and OS abstractions: Code that abstracts platform- or hardware-specific functionality, like accessing sensors, file storage, etc., so it can be used uniformly across different platforms.

Keep in mind that choosing which parts to share depends on the specific needs of your project and team. KMP gives you the flexibility to adapt your code-sharing strategy as the project evolves.
Final thoughts
In this article, we went from zero into the KMP world and built a technical understanding of the difference between native, cross-platform, and multiplatform development.
In short, each technology—React Native, Flutter, and Kotlin Multiplatform—has its strengths and weaknesses.
When choosing the right tool for your project, consider factors like performance, ease of use, and community support. Kotlin Multiplatform emerges as a promising option, especially for those who value the efficiency of shared code without compromising the native user experience.
With this knowledge, we can move on to the more specific concepts of how Kotlin Multiplatform works, like the compiler, syntax, configuration, and so on.
Next steps
We’ll learn how the Kotlin compiler works, and how its frontend + backend + IR structure makes the multiple compilations possible.
🤖 This article was written with the help of ChatGPT 4, using the Web plugin.
The sources and content are reviewed to ensure the information provided is relevant, as are the sources used in each prompt.
That said, if you find any incorrect information or believe a credit is missing, please get in touch!
References