Given a hierarchy `iosArm64Main → iosMain → appleMain → nativeMain → commonMain`, where should you place an `actual` for an `expect` declared in commonMain, and what trade-offs guide that choice?
answer
- Place actual at highest level that still has the APIs
- nativeMain=POSIX, appleMain=Foundation, leaf=device-specific
- Exactly one actual per leaf chain
- actuals do NOT override each other
- Higher=DRY/narrow API; lower=full API/duplication
basics
~20 sPut the actual as high up the chain as possible so it is shared by all platforms that can use it. Push it down to a specific level only when an implementation truly differs there.
solid answer
~50 sAn `actual` can satisfy a common `expect` from ANY source set on the leaf's `dependsOn` chain. Rule of thumb: place it at the **highest** (most-shared) level whose APIs are still available. If the implementation uses only POSIX, put the `actual` in `nativeMain`; if it needs Apple Foundation (e.g. `NSDate`, `platform.Foundation.*`), put it in `appleMain`; only put it in `iosArm64Main` when behavior is genuinely device-specific. Higher placement reduces duplication and keeps one source of truth, but pulls in fewer available platform APIs (commonMain/intermediate sets see only a narrower API surface). You can also mix: a default `actual` in `appleMain` plus an overriding... no — actuals don't override; instead you'd push the `expect`/`actual` boundary down or use an `expect`-less common interface with intermediate implementations. The compiler enforces exactly ONE `actual` reachable per leaf; two `actual`s on the same chain is a compile error.
go deeper
Knows actuals live in platform source sets and one is needed per platform.
Understands actuals can live in intermediate sets and that exactly one per leaf is required.
Chooses placement by API availability vs. sharing, knows actuals don't override, and uses interface+factory when behavior diverges.
Designs the expect/actual vs. interface boundary across a large target matrix to minimize drift and duplication, and reasons about API-surface and maintainability trade-offs.
## The core rule For any **leaf** target, the compiler unions `commonMain` plus every source set the leaf `dependsOn` transitively. An `expect` in `commonMain` is satisfied if **exactly one** `actual` for it exists somewhere on that union. So you have a choice of where to write it. ## Decide by API availability + sharing Go as HIGH (shared) as the required platform APIs allow: - **`nativeMain`** — sees the Kotlin/Native + interop (e.g. POSIX via `platform.posix.*`). Use when the impl is pure-native and identical across all native targets (iOS, macOS, linux, mingw...). - **`appleMain`** — adds Apple Foundation/Darwin (`platform.Foundation.NSDate`, `platform.darwin.*`). Use when the impl needs Apple frameworks but is the same on every Apple device. - **`iosMain`** — narrows to iOS device + simulator. - **`iosArm64Main`** (leaf) — only when behavior must differ on the physical-device target specifically. ```kotlin // commonMain expect fun epochMillis(): Long // appleMain (shared across iOS/macOS/watchOS/tvOS) -- needs Foundation import platform.Foundation.NSDate actual fun epochMillis(): Long = (NSDate().timeIntervalSince1970 * 1000).toLong() // jvmMain -- separate chain, needs its own actual actual fun epochMillis(): Long = System.currentTimeMillis() ``` ## Trade-offs - **Higher = less duplication, single source of truth**, but a narrower visible API (you can't call iOS-only APIs from `nativeMain`). - **Lower = full platform API access**, but you repeat the `actual` across siblings, risking drift. ## Hard constraints - **Exactly one `actual` per leaf chain.** Putting an `actual` in both `appleMain` and `iosArm64Main` is a duplicate-actual compile error for the iOS leaf. - **`actual`s do NOT override.** Unlike open/override methods, you cannot have a fallback `actual` overridden by a more specific one. If you need variation, either move the `expect` deeper, or replace `expect`/`actual` with a common `interface` + per-source-set implementations chosen via a factory `expect fun`. - **An `expect` with a default-ish shared body**: `expect` declarations can't have bodies (except since recent Kotlin, `expect` functions may have default parameter values declared in common). Implementation always lives in `actual`. ## Practical pattern Default to writing the `actual` in the most-shared intermediate set; demote it only when a concrete sibling genuinely diverges — that keeps the hierarchy DRY and makes each leaf still see exactly one valid implementation.
- You wrote one actual in appleMain and another in iosArm64Main for the same expect. What happens?The iOS leaf sees two actuals on its chain — a duplicate-actual compile error. Keep exactly one per chain.
- How do you get behavior that differs only on one specific native target without duplicating everything?Replace the expect with a common interface and a factory expect fun; provide the shared impl high and a divergent one in the specific source set via that factory.
Like putting shared utilities in the most general base class that still has the tools you need — not so high you lose access, not so low you copy-paste.
saying these in an interview costs you the question
- Believing a higher-level actual is overridden by a lower one
- Always writing actuals only in leaf source sets (duplication)
- Putting two actuals on the same chain
- Trying to call iOS-only APIs from nativeMain
- Thinking expect declarations carry implementation bodies