expect/actual Mechanism for Platform APIs
Declare expected declarations in common code and provide actual implementations per platform.
expect/actual Mechanism for Platform APIs is a free Kotlin Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The expect/actual Pattern
expect declares an API contract in commonMain. Each platform provides an actual implementation. The compiler enforces that all targets have an actual.
// commonMain:
expect fun platformName(): String
// androidMain:
actual fun platformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"
// iosMain:
actual fun platformName(): String = UIDevice.currentDevice.systemName()expect class
expect class declares a class with its API in commonMain. Each platform provides the full implementation.
// commonMain:
expect class DateTime {
fun format(pattern: String): String
val timestamp: Long
}
// androidMain:
actual class DateTime {
actual val timestamp = System.currentTimeMillis()
actual fun format(pattern: String): String =
java.text.SimpleDateFormat(pattern).format(java.util.Date(timestamp))
}expect object
expect object declares a singleton with its contract. Useful for platform-specific utilities like logging, settings, or analytics.
// commonMain:
expect object Logger {
fun debug(msg: String)
fun error(msg: String, cause: Throwable? = null)
}
// androidMain:
actual object Logger {
actual fun debug(msg: String) = android.util.Log.d("App", msg)
actual fun error(msg: String, cause: Throwable?) =
android.util.Log.e("App", msg, cause)
}expect val / fun at Top Level
Top-level expect val and expect fun are simple and work well for stateless platform utilities.
// commonMain:
expect val isDebugBuild: Boolean
expect fun generateUUID(): String
// androidMain:
actual val isDebugBuild: Boolean = BuildConfig.DEBUG
actual fun generateUUID(): String = java.util.UUID.randomUUID().toString()
// iosMain:
actual val isDebugBuild: Boolean = false // or a native flag
actual fun generateUUID(): String = NSUUID().UUIDString()expect fun with Default Implementation
Since Kotlin 1.9+, expect fun can have a default implementation via expect fun f() { }. Platforms can override or use the default.
// commonMain (Kotlin 1.9+):
expect fun log(msg: String) {
println("[DEFAULT] $msg") // used if no actual overrides
}
// androidMain:
actual fun log(msg: String) = android.util.Log.d("KMP", msg)
// iosMain (uses default):
// no actual needed — default is usedActual Typealias
If a platform already has the exact class you need, use actual typealias to point to it instead of wrapping it.
// commonMain:
expect class AtomicInt {
fun get(): Int
fun incrementAndGet(): Int
}
// androidMain / jvmMain:
actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger
// iosMain:
actual class AtomicInt {
private var value = 0
actual fun get() = value
actual fun incrementAndGet() = ++value
}expect interface (indirect pattern)
For more flexibility, declare an interface in commonMain and use expect/actual only for the factory that creates the platform instance.
// commonMain:
interface FileStorage {
suspend fun readFile(path: String): ByteArray
suspend fun writeFile(path: String, data: ByteArray)
}
expect fun createFileStorage(): FileStorage
// androidMain:
actual fun createFileStorage(): FileStorage = AndroidFileStorage()
// iosMain:
actual fun createFileStorage(): FileStorage = IosFileStorage()Testing with expect/actual
In commonTest, test the common API. Actual implementations run during platform-specific test tasks (testDebugUnitTest on Android, iosX64Test on iOS).
// commonTest:
import kotlin.test.Test
import kotlin.test.assertNotNull
class PlatformTest {
@Test
fun testUUID() {
val uuid = generateUUID()
assertNotNull(uuid)
println("UUID: $uuid")
}
}Common Pitfalls
Every declared expect must have an actual in ALL configured targets, or the build fails. Missing actuals are caught at compile time.
// If you add a new target (e.g., jvmMain), you must add:
// actual fun platformName(): String = "JVM"
// Otherwise:
// error: Expected function platformName has no actual declaration in module :shared for JVM
fun main() { println("Missing actuals = compile error") }Expect Annotation Class
expect annotation class allows platform-specific annotation mapping — for example, mapping a common @Parcelize-like annotation to the Android Parcelize plugin.
// commonMain:
expect annotation class CommonParcelize()
// androidMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize
// iosMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelizeReal-World: Keychain/Preferences
A typical real-world expect/actual: a secure storage interface where Android uses EncryptedSharedPreferences and iOS uses Keychain.
// commonMain:
expect class SecureStorage {
fun save(key: String, value: String)
fun get(key: String): String?
}
// androidMain:
actual class SecureStorage {
private val prefs = /* EncryptedSharedPreferences */ mapOf<String,String>()
actual fun save(key: String, value: String) { /* ... */ }
actual fun get(key: String): String? = null // simplified
}Quick Check
What happens at compile time if an expect declaration has no actual for a target?
Recap
expect declares the API in commonMain. actual provides the platform implementation. Use actual typealias when the platform already has the needed type. Missing actuals are caught at compile time — no runtime surprises.
Frequently asked questions
Is the “expect/actual Mechanism for Platform APIs” lesson free?
Yes — the full text of “expect/actual Mechanism for Platform APIs” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.
What will I learn in “expect/actual Mechanism for Platform APIs”?
Declare expected declarations in common code and provide actual implementations per platform. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Kotlin Academy?
No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “expect/actual Mechanism for Platform APIs” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Kotlin Academy lesson?
Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- KMP Project Structure: commonMain, androidMain, iosMain
- expect/actual Mechanism for Platform APIs
- Sharing Repository and Use Case Layers
- Dependency Injection in KMP with Koin