skip to content

What compilation targets does KMP support, and what artifact does each produce?

level: seniorimportance: should knowfreq 45%

answer

  1. Targets: JVM/Android, Native (LLVM), JS(IR), Wasm
  2. JVM→bytecode, Native→framework/binary, JS→ES modules, Wasm→.wasm
  3. Apple slices bundled into XCFramework
  4. Native is AOT, no JVM at runtime
  5. Gradle metadata picks the right variant for consumers

basics

~10 s

A target is a platform you build for. JVM produces bytecode, Native produces machine-code binaries (like an iOS framework), JS produces JavaScript, and Wasm produces WebAssembly. You declare targets in Gradle.

solid answer

~40 s

In the `kotlin { }` block you declare **targets**: `jvm()`/`androidTarget()`, native ones (`iosArm64()`, `iosSimulatorArm64()`, `macosArm64()`, `linuxX64()`, `mingwX64()`), `js(IR)`, and `wasmJs()`/`wasmWasi()`. Each emits a different artifact: JVM → `.class`/`.jar` bytecode; Kotlin/Native → static/dynamic libraries or an Apple **`.framework`** (often packaged as an **XCFramework** for Swift); JS → ES modules consumable by npm/browser; Wasm → a `.wasm` binary. Native targets compile ahead-of-time via **LLVM**, so there's no JVM at runtime. Android isn't a separate compiler back-end — it reuses the JVM back-end via `androidTarget()`. Each target has its own dependencies and can be published as a Gradle artifact with platform-specific metadata so consumers pick the right variant automatically.

go deeper

for a junior

Names a few targets and that each builds a different output.

for a middle

Maps each target family to its artifact and knows native uses LLVM/AOT.

for a senior

Configures targets in Gradle, handles XCFramework/simulator-vs-device, and explains metadata-based variant selection.

for a principal

Plans the target matrix for a product, weighs build cost, publishing strategy, and runtime-model differences across targets.

## Targets = platforms you compile for A **target** tells the Kotlin compiler which platform to produce output for. You declare them inside the `kotlin { }` Gradle block; each target gets its own source sets and dependency scope. ## The target families and their artifacts - **JVM** — `jvm()` for server/desktop, `androidTarget()` for Android. Output: **JVM bytecode** (`.class` files in a `.jar` or an Android library/`.aar`). Android shares the JVM back-end; it's not a distinct compiler back-end. - **Kotlin/Native** — `iosArm64()`, `iosSimulatorArm64()`, `iosX64()`, `macosArm64()`, `linuxX64()`, `mingwX64()` (Windows), `watchosArm64()`, etc. Compiled **ahead-of-time through LLVM** into machine code. Output: a static/shared library, or on Apple an **`.framework`**. Multiple architectures are bundled into an **XCFramework** so an iOS app links one artifact. - **JavaScript** — `js(IR)` produces **JavaScript** (ES modules) for browser/Node, consumable as an npm package. - **WebAssembly** — `wasmJs()` (browser, JS interop) and `wasmWasi()` (WASI runtimes) produce a **`.wasm`** binary. Wasm is the newest family and powers Compose for Web. ## Runtime model differs by target - JVM targets need a JVM at runtime and get JIT/GC from it. - Native targets are **self-contained binaries** — no JVM. Kotlin/Native ships its own memory manager and GC. - JS/Wasm run in the browser/JS engine or a Wasm runtime. ```kotlin kotlin { androidTarget() iosArm64(); iosSimulatorArm64() jvm("desktop") js(IR) { browser() } wasmJs { browser() } // bundle iOS slices into one XCFramework // (configured via the xcframework() helper in cocoapods/framework setup) } ``` ## Publishing and variant selection When you publish a KMP library, Gradle attaches **Kotlin/Gradle metadata** describing each variant. A consumer that depends on your library automatically receives the correct artifact for its own target — JVM consumers get the JVM jar, iOS consumers get the framework slice — without manual classifier juggling. The root `*-kotlin-multiplatform` artifact carries this metadata. ## Practical notes - iOS needs **both** a device target (`iosArm64`) and a simulator target (`iosSimulatorArm64`) for development. - Apple-silicon vs Intel matters for simulator targets (`iosSimulatorArm64` vs `iosX64`). - `js(IR)` is the modern JS back-end (the legacy back-end is removed).

  • Why declare both iosArm64 and iosSimulatorArm64?
    Device builds use the arm64 device target; running in the simulator on Apple silicon needs the simulator-arm64 target — they are separate binaries.
  • Is Android a separate compiler back-end?
    No — androidTarget() reuses the JVM back-end; the difference is Android packaging (.aar) and the Android-specific dependency scope.

saying these in an interview costs you the question

  • Claiming native targets run on a JVM at runtime
  • Thinking there is one universal binary instead of per-target artifacts
  • Not knowing iOS needs separate device and simulator targets
  • Confusing js(IR) with the removed legacy JS back-end
  • Believing consumers must manually pick artifact classifiers

context