skip to content

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?

level: seniorimportance: should knowfreq 45%

answer

  1. Place actual at highest level that still has the APIs
  2. nativeMain=POSIX, appleMain=Foundation, leaf=device-specific
  3. Exactly one actual per leaf chain
  4. actuals do NOT override each other
  5. Higher=DRY/narrow API; lower=full API/duplication

basics

~20 s

Put 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 s

An `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

for a junior

Knows actuals live in platform source sets and one is needed per platform.

for a middle

Understands actuals can live in intermediate sets and that exactly one per leaf is required.

for a senior

Chooses placement by API availability vs. sharing, knows actuals don't override, and uses interface+factory when behavior diverges.

for a principal

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

context