← All courses

expect / actual: Platform-Specific Code

🗓 May 31, 2026 ⏱ 2 min read

Why you need this

Most of your shared code is fully common, but occasionally it needs something only the platform provides — the device name, secure storage, a UUID generator, the current platform’s database driver. The expect / actual mechanism lets common code declare a need and each platform provide the implementation.

The pattern

In commonMain you write expect (a declaration with no body). In each platform source set you write actual (the real implementation).

// commonMain — the contract
expect fun platformName(): String

// androidMain — Android's implementation
actual fun platformName(): String =
    "Android ${android.os.Build.VERSION.SDK_INT}"

// iosMain — iOS's implementation
actual fun platformName(): String =
    UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion

Common code simply calls platformName(); at compile time, each platform plugs in its own version. The rest of your shared code stays blissfully unaware of the difference.

expect/actual for classes

You can do the same with whole classes — useful for things like a settings store or a database driver factory:

// commonMain
expect class Platform() {
    val name: String
}

// androidMain
actual class Platform actual constructor() {
    actual val name: String = "Android"
}

// iosMain
actual class Platform actual constructor() {
    actual val name: String = "iOS"
}

The better alternative: interfaces + injection

For anything beyond trivial cases, many teams prefer a cleaner approach: define an interface in common, implement it per platform, and pass the implementation in (dependency injection). This is more flexible and easier to test than expect/actual.

// commonMain
interface Analytics { fun log(event: String) }

// each platform provides its own Analytics implementation,
// injected into shared code at startup.

When to use which

  • expect/actual — small, self-contained platform differences (UUID, platform name, a factory).
  • interface + injection — larger services with dependencies, or anything you want to mock in tests.

Keep platform code minimal

The golden rule: put as much as possible in commonMain, and only drop into expect/actual (or interfaces) when you genuinely need a platform API. Every bit of platform-specific code is code you write more than once.

Common mistakes

  • Overusing expect/actual for things that could stay common.
  • Forgetting to provide an actual for every target (it won’t compile).
  • Reaching for expect/actual when an injected interface would be cleaner and testable.
Summary: expect declares a platform need in common code; actual implements it per platform. Use it for small platform differences, prefer interfaces + injection for larger services, and keep platform-specific code to a minimum.