You ship the same feature twice. Once for iOS in Swift. Once for Android in Kotlin. The logic is identical — validation, calculations, state machines — but the code lives in two repos, drifts apart, and breaks in different ways. Kotlin Multiplatform (KMP) kills this duplication by letting you write business logic once in Kotlin and compile it to native binaries for both platforms.
How KMP Actually Works
KMP isn't a UI framework. It's a compilation target. Your shared module compiles to a Kotlin/Native framework for iOS and a JAR/AAR for Android. The Gradle plugin handles the heavy lifting: kotlin("multiplatform") configures source sets like commonMain, iosMain, and androidMain. Platform-specific code goes in its own source set; shared code stays in common.
The expect/actual mechanism bridges platform gaps. You declare expect in common, implement actual in each platform source set. No reflection, no runtime overhead — just compile-time dispatch.
What to Share (and What Not To)
Share pure logic: data models, serialization, networking clients, database repositories, business rules, analytics events. Keep platform UI, permissions, lifecycle, and hardware APIs separate. A typical split: 60-70% shared code, 30-40% platform-specific.
| Layer | Share? | Example | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Domain models | Yes | data class User(...) | Networking | Yes | Ktor client + serialization | Database | Yes | SQLDelight repositories | Business rules | Yes | PricingEngine, Validators | UI | No | SwiftUI / Compose Multiplatform | Permissions | No | Location, Camera, Bluetooth | Platform SDKs | No | HealthKit, Google Play Services |
Compose Multiplatform Changes the Math
Compose Multiplatform extends sharing to UI. Write composables once in commonMain, render natively on Android (via Compose) and iOS (via Skia). It's production-ready for shared design systems, feature screens, and even whole apps — but evaluate carefully. iOS performance is solid for most CRUD apps; complex animations or heavy lists may still need native SwiftUI.
"We moved our checkout flow to Compose Multiplatform. Two engineers now maintain what took four. iOS users see zero difference.
— Lead Engineer, Fintech Unicorn
Integration Patterns for Existing Apps
Brownfield adoption works. Publish the shared module as an XCFramework for iOS (via cocoapods or SPM) and an AAR for Android. Consume it like any third-party dependency. No big-bang rewrite required.
Pitfalls That Kill Projects
Gradle complexity. KMP builds are slower. Cache aggressively. Use configuration cache. Binary size. Each KMP framework adds ~2-4MB. Strip unused architectures. Debugging. Step-through from Swift into Kotlin works in Android Studio / Fleet, but Xcode integration is limited. Team skills. iOS devs must read Kotlin. Pair program the first month.
✦
Your Next Steps
1. Create a shared Gradle module in your Android repo. 2. Move one network client + DTOs to commonMain. 3. Add iosMain with actual implementations for platform APIs. 4. Publish locally, consume from Xcode. 5. Measure: build time, binary size, bug recurrence. 6. Expand incrementally.
The duplication tax is optional. KMP lets you pay it once in Kotlin, then collect dividends on every platform.