You want to write your app once and run it everywhere. This is the promise of every multiplatform tool. But not all “shared code” is the same.
When you choose a tool to share code, you are actually choosing whose runtime will run your app. This choice decides whether your app feels native, how it performs, and how much overhead it carries.
Most frameworks bring their own layer. Flutter brings the Impeller engine to draw pixels. React Native brings a JavaScript engine to run logic. These layers make the code portable because they provide a consistent environment regardless of the platform. However, they sit between your app and the platform. This creates a dependency on the framework’s own runtime.
Kotlin Multiplatform (KMP) is different. It does not bring a new runtime. Instead, it compiles your Kotlin code into the artifact the platform already runs. If you target Android, it produces an Android library. If you target iOS, it produces a native framework.
To understand how this works, you need to be clear about three terms: the machine, the virtual machine, and the target.
A machine is the physical CPU and the operating system. This is the lowest level of execution. A virtual machine (VM) is software that runs code in place of the machine. The Java Virtual Machine (JVM) is the most common example. It executes Java bytecode. This allows the same code to run on any machine that has a JVM installed.
In KMP, a target is a build decision. When you define a target in your build configuration, you tell the Kotlin compiler to produce an artifact for a specific platform. The compiler does not just “wrap” the code. It uses a specific backend to translate Kotlin into the language of that target.
Every platform has a file format it understands. Android apps use APKs and libraries use AARs. Java backends use JARs. Apple platforms use Frameworks. System programs use shared libraries like .so, .dylib, or .dll files.
KMP speaks all these languages.
This capability allows KMP to integrate into existing projects without forcing a total rewrite. You can add a KMP module to an existing iOS app. The iOS app sees it as just another native framework.
KMP does not force you to choose between “shared” and “native.” It gives you both through a system of source sets.
You write the bulk of your logic in a source set called commonMain. This code is platform-agnostic. It contains the business logic, data models, and networking code that are identical across all platforms.
But some things are never identical. You might need to get the device name, access a secure keychain, or use a platform-specific database. For these cases, KMP uses the expect and actual keywords.
You declare an expect function in commonMain. This is a promise to the compiler that every target will provide an implementation. Then, you provide the actual implementation in each platform source set, such as iosMain or androidMain.
The build system then takes this combined source and runs a per-target compilation.
This process turns one source into many artifacts. The compiler uses an Intermediate Representation (IR) to understand the logic. It then applies a backend for each target.
A single KMP project can produce a .jar for the JVM, an .aar for Android, a .kexe for macOS, and a .js file for the web. These are not wrappers. They are the native binaries of those platforms.
Consider a simple project that greets the user with the name of the platform. The shared code declares that it expects a platformName() function. The JVM implementation returns “JVM”, the Android implementation returns “Android”, and the Native implementation returns “Kotlin/Native”.
> Task :bundleReleaseAar
> Task :jvmJar
> Task :compileProductionExecutableKotlinJs
> Task :linkReleaseExecutableMacosArm64
> Task :linkReleaseSharedLinuxX64
> Task :linkReleaseSharedMacosArm64
> Task :linkReleaseSharedMingwX64
> Task :assemble
BUILD SUCCESSFUL in 32s
84 actionable tasks: 70 executed, 14 up-to-date build/libs/what-is-multiplatform-jvm.jar
build/outputs/aar/what-is-multiplatform-release.aar
build/bin/macosArm64/releaseExecutable/what-is-multiplatform.kexe
build/bin/macosArm64/releaseShared/libwhat_is_multiplatform.dylib
build/bin/linuxX64/releaseShared/libwhat_is_multiplatform.so
build/bin/mingwX64/releaseShared/what_is_multiplatform.dll
build/js/packages/what-is-multiplatform/kotlin/what-is-multiplatform.jsWhen you run these artifacts, they do not ask a KMP runtime for help. They run directly on the platform.
Hello from Kotlin/Native
Hello from JavaScript
Hello from JVMThe binary formats prove this. A KMP-produced Linux library is a standard ELF shared object. A Windows library is a PE32+ DLL. A macOS binary is a Mach-O executable. These are the same formats produced by C++ or Rust compilers.
build/bin/linuxX64/releaseShared/libwhat_is_multiplatform.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=f9d22cf0a21abf6b6d73a93bff2bfb4fad71dacd, not stripped
build/bin/mingwX64/releaseShared/what_is_multiplatform.dll: PE32+ executable (DLL) (GUI) x86-64, for MS Windows
build/bin/macosArm64/releaseShared/libwhat_is_multiplatform.dylib: Mach-O 64-bit dynamically linked shared library arm64
build/bin/macosArm64/releaseExecutable/what-is-multiplatform.kexe: Mach-O 64-bit executable arm64
build/libs/what-is-multiplatform-jvm.jar: Zip archive data, at least v2.0 to extract, compression method=deflate
build/outputs/aar/what-is-multiplatform-release.aar: Zip archive data, at least v2.0 to extract, compression method=deflateIf you look inside an Android AAR produced by KMP, you find a standard classes.jar and a manifest. The Android Runtime (ART) executes this bytecode exactly as it would for a library written in Java.
Archive: build/outputs/aar/what-is-multiplatform-release.aar
Length Date Time Name
--------- ----- ----- ----
0 02-01-1980 00:00 R.txt
217 02-01-1980 00:00 AndroidManifest.xml
1652 02-01-1980 00:00 classes.jar
156 02-01-1980 00:00 META-INF/com/android/build/gradle/aar-metadata.properties
--------- -------
2025 4 filesThis architecture removes the “framework tax.” There is no bridge to cross and no virtual machine to start inside your app.
The result is a fundamental shift in how you share code.
In other frameworks, the framework understands the code, and the framework talks to the platform. In KMP, the platform understands the code.
Kotlin Multiplatform is not a framework that wraps your app. It is a compiler technology that makes Kotlin one more language the platform understands.