Agency

Architecting With Kotlin Multiplatform: Building Shared Codebases Without Sacrificing Native Experiences

Learn how to share business logic across Android, iOS, and other platforms using Kotlin Multiplatform while keeping native UI and capabilities where they matter.

LAST UPDATED: March 12, 2026
7 min read
Architecting With Kotlin Multiplatform: Building Shared Codebases Without Sacrificing Native Experiences

Kotlin Multiplatform (KMP) has matured into a practical architecture for teams that want to share business logic across Android, iOS, and other platforms while keeping platform-specific UI and capabilities where they matter. The real challenge is not deciding how much code to share—it is designing boundaries that maximize reuse without turning every platform into the same application.

Why Kotlin Multiplatform Matters

Building applications for multiple platforms traditionally means maintaining separate codebases.

For example:

Android
   ↓
Kotlin

iOS
   ↓
Swift

That gives each platform maximum flexibility, but it can also create duplicated business logic.

The same rules may need to be implemented twice:

Authentication

Networking

Validation

Data models

Caching

Synchronization

Business rules

Over time, the implementations can drift.

One platform may receive a bug fix while another does not.

Kotlin Multiplatform offers another approach:

                Shared Kotlin
                     │
          ┌──────────┴──────────┐
          ▼                     ▼
       Android                 iOS
          │                     │
     Native UI             Native UI

The goal is not necessarily to share everything.

It is to share the parts that benefit from being implemented once.

KMP is most powerful when code sharing follows architectural boundaries rather than forcing platforms into identical implementations.

What Kotlin Multiplatform Actually Shares

Kotlin Multiplatform allows Kotlin code to target multiple platforms.

A shared module might contain:

shared/
 ├── domain/
 ├── data/
 ├── networking/
 ├── models/
 └── validation/

Meanwhile, platform applications can contain:

Android
 ├── UI
 └── Android Integrations

iOS
 ├── UI
 └── Apple Integrations

This creates a useful separation.

Shared Layer

Owns:

Business rules

Models

Networking

Persistence abstractions

Validation

Use cases

Platform Layer

Owns:

Native UI

Platform APIs

Device integrations

Platform-specific behavior

This architecture lets teams reuse logic without eliminating native platform capabilities.

Designing the Right Architecture

A strong KMP architecture often follows a layered model:

             Presentation
            /            \
       Android            iOS
          │                │
          └──────┬─────────┘
                 ▼
              Domain
                 │
                 ▼
               Data
                 │
        ┌────────┼────────┐
        ▼        ▼        ▼
    Network   Storage   Services

The shared domain should not depend on Android or iOS UI frameworks.

For example:

Domain
 ├── User
 ├── Order
 ├── Checkout
 └── Authentication

The UI consumes the domain rather than owning the business rules itself.

This makes shared code easier to test and reuse.

Shared Business Logic vs. Platform-Specific Code

One of the most important KMP decisions is deciding what not to share.

A good candidate for sharing:

CalculateOrderTotal()
ValidateEmail()
LoadUserProfile()
SubmitPayment()

A questionable candidate:

Complex Native UI
Platform Navigation
Platform Animation
Platform-Specific Interaction

The question should not be:

"Can we share this?"

Instead ask:

"Should we share this?"

A useful rule is:

Business Logic
     ↓
Usually Shared

Platform Experience
     ↓
Often Native

This allows iOS and Android teams to build experiences that feel natural on their respective platforms.

Expect and Actual: Bridging Platform Capabilities

Sometimes shared code needs a platform-specific implementation.

For example, imagine the application needs a secure device identifier.

The shared code can define the expected capability:

expect class DeviceInfo {
    fun identifier(): String
}

Each platform provides its own implementation.

Conceptually:

              Shared API
                  │
              expect
             /      \
            /        \
     Android          iOS
      actual          actual

This creates a controlled boundary between shared logic and platform-specific capabilities.

The important principle is to keep these boundaries small.

If the shared module constantly asks the platform layer for dozens of tiny implementation details, the architecture can become difficult to understand.

Networking, Storage, and Data Layers

Networking is one of the strongest candidates for shared KMP code.

A typical architecture might look like:

             Repository
                 │
                 ▼
              API Client
                 │
        ┌────────┴────────┐
        ▼                 ▼
      Server           Local Cache

Shared code can handle:

API models

Serialization

HTTP communication

Error mapping

Repository logic

Caching rules

For persistence, teams can define shared abstractions while allowing platform-appropriate implementations.

For example:

Shared Repository
       │
       ▼
Storage Interface
    /       \
   /         \
Android      iOS
Storage      Storage

This keeps business logic independent from the underlying storage mechanism.

Sharing UI With Compose Multiplatform

Kotlin Multiplatform does not require teams to share UI.

You can use:

Shared Kotlin
     │
 ┌───┴────┐
 ▼        ▼
Android  iOS
 │        │
Compose  SwiftUI

This is often attractive when teams want highly native platform experiences.

Alternatively, Compose Multiplatform can allow UI code to be shared across supported platforms.

That architecture can look like:

              Shared
        ┌───────────────┐
        │ Logic + UI    │
        └───────┬───────┘
                │
        ┌───────┴───────┐
        ▼               ▼
     Android           iOS

The decision should depend on the product.

Share UI when:

Visual consistency is important

Teams want a common UI implementation

The interaction model is similar across platforms

Keep UI native when:

Platform conventions matter heavily

The experiences differ substantially

Teams need maximum platform-specific flexibility

There is no requirement to choose one extreme.

A hybrid architecture can be very effective.

Dependency Management and Project Structure

A KMP project can become complicated if dependency boundaries are unclear.

A scalable structure might look like:

project/
 ├── shared/
 │    ├── domain/
 │    ├── data/
 │    └── core/
 │
 ├── androidApp/
 │
 └── iosApp/

For larger organizations, shared modules can be split further:

shared/
 ├── authentication/
 ├── networking/
 ├── payments/
 ├── profile/
 └── analytics/

But modularization should have a purpose.

Too few modules create large, tightly coupled components.

Too many modules create unnecessary build and dependency complexity.

A good module should have:

Clear ownership

A clear API

A clear responsibility

Testing Shared Code

One of KMP's biggest advantages is that business logic can be tested once.

For example:

Shared Business Rules
       ↓
Unit Tests
       ↓
Android + iOS

Test areas such as:

Validation

Authentication logic

Data transformation

Repository behavior

Business rules

Error handling

Platform-specific code should still have platform-specific tests.

A good testing model is:

Shared Logic
    ↓
Shared Tests

Android Integration
    ↓
Android Tests

iOS Integration
    ↓
iOS Tests

The objective is not to eliminate platform testing.

It is to avoid testing identical business logic separately on every platform.

Performance and App Size

Sharing code does not automatically mean better performance.

KMP applications still need to be evaluated according to platform-specific runtime behavior.

Important areas include:

Startup time

Memory usage

Network performance

Serialization

Database operations

Concurrency

Binary size

For example, sharing a large dependency simply because it works across platforms can increase application size unnecessarily.

A good architecture asks:

Does sharing this capability actually reduce total complexity and cost?

Measure the result.

Do not assume.

Common KMP Architecture Mistakes

Sharing Everything

More shared code does not automatically mean a better architecture.

Treating Android and iOS as Identical

They have different conventions, capabilities, and user expectations.

Putting UI Logic Into the Domain Layer

Business logic should remain independent from presentation.

Creating a Giant Shared Module

A huge shared module eventually becomes difficult for multiple teams to own.

Excessive Platform Bridges

Too many `expect`/`actual` boundaries can indicate that abstractions are not well designed.

Ignoring Native Capabilities

Do not avoid excellent platform APIs simply because they are not cross-platform.

Choosing Shared UI Before Understanding the Product

Compose Multiplatform can be powerful, but UI sharing should follow product needs.

Assuming Shared Code Means No Platform Testing

Platform integrations still need real platform testing.

A Practical Adoption Strategy

Step 1: Identify Duplicate Logic

Find functionality currently implemented independently on Android and iOS.

Look for:

Networking

Models

Validation

Authentication

Business rules

Step 2: Choose a Small Starting Point

For example:

Existing Android
Existing iOS
      ↓
Share Networking
      ↓
Validate Architecture

Avoid rewriting the entire application immediately.

Step 3: Define the Shared Boundary

Decide what belongs in:

Domain

Data

Platform

UI

Step 4: Introduce Shared Tests

Prove that the shared business layer behaves consistently.

Step 5: Add Platform Implementations

Keep device-specific functionality behind explicit interfaces.

Step 6: Evaluate UI Sharing Separately

Do not assume shared business logic requires shared UI.

Step 7: Measure Developer and Product Impact

Track:

Code reuse

Feature delivery time

Defect rates

Build times

App performance

Team productivity

Step 8: Expand Gradually

Once the architecture proves itself, move additional domains into shared modules.

Networking
   ↓
Authentication
   ↓
Domain
   ↓
Persistence
   ↓
Additional Features

The Future of Kotlin Multiplatform

KMP is part of a broader shift toward shared business capabilities with platform-specific experiences.

A mature architecture can look like:

                  Product
                     │
             Shared Domain
                     │
        ┌────────────┼────────────┐
        ▼            ▼            ▼
     Android        iOS         Other
        │            │            │
     Native /    Native /      Platform
     Shared UI   Shared UI     UI

This model gives teams several options.

They can share:

Core logic

Networking

Persistence

Models

Analytics

Design-system components

and keep specialized capabilities native.

AI-powered applications, offline-first products, financial applications, and connected-device experiences can particularly benefit from shared domain logic because consistency across platforms becomes increasingly important.

The bigger opportunity is architectural:

One business model, multiple excellent platform experiences.

Making the Call

Engineering leaders considering Kotlin Multiplatform should ask:

Which logic is duplicated across platforms today?

Which parts genuinely benefit from one implementation?

Where do Android and iOS need different experiences?

How much platform-specific code will remain?

Who owns the shared modules?

How will shared dependencies be governed?

Will UI also be shared, or only business logic?

How will the team test platform integrations?

Most importantly:

Are we adopting KMP to solve a real architectural problem, or simply because code sharing sounds attractive?

Final Takeaway

Kotlin Multiplatform is most effective when it creates the right balance:

                 Shared Logic
                      │
       ┌──────────────┼──────────────┐
       ▼              ▼              ▼
   Networking       Domain        Storage
       │              │              │
       └──────────────┼──────────────┘
                      │
             ┌────────┴────────┐
             ▼                 ▼
          Android              iOS
             │                 │
        Native / Shared   Native / Shared
             UI                 UI

Share what should be consistent.

Keep platform-specific capabilities where they provide real value.

Define clean module boundaries.

Test shared logic once and platform integrations where they actually run.

Avoid turning the shared layer into a massive dependency that every team must understand.

And remember that successful multiplatform architecture is not measured by the percentage of code shared.

The goal is not maximum code reuse. The goal is maximum engineering leverage without sacrificing product quality.

Kotlin Multiplatform gives teams the flexibility to build one shared foundation while still respecting what makes Android and iOS different.

Share the logic that benefits from consistency. Keep the experiences that benefit from being native. Build clear boundaries. And let architecture—not ideology—decide what should be shared.

Frequently Asked Questions

No, Kotlin Multiplatform does not force you to share UI code. You can choose to share only business logic, networking, and data layers while building native UIs using SwiftUI for iOS and Jetpack Compose for Android. If you do want to share UI, you can use Compose Multiplatform as an optional layer.
KMP uses the 'expect' and 'actual' mechanism to bridge shared logic with native capabilities. You define an 'expect' interface or function in your shared code, and provide the 'actual' implementation in each platform-specific module (Android, iOS, etc.), allowing you to access native APIs seamlessly.
Not inherently, but it depends on your architecture. While KMP allows you to compile to native binaries (like iOS frameworks via Kotlin/Native) and Android bytecode, indiscriminately sharing large dependencies can increase binary size. Performance is typically near-native, but you must evaluate factors like serialization overhead and memory management boundaries.

Need a product built?

We build custom software, mobile apps, and web platforms for startups and enterprises.

Alejandro D.
Vatsalya R.Backend Developer
Gustavo A.
Ganeshan S.Sr. Software Engineer
Fiorella G.
Uptal JoshiSr. Data Scientist

Their team became an extension of ours — within months they'd rebuilt our entire product experience from the ground up.

BitForge
Sr. ArchitectBitForge
Read Case Study