How does the compiler decide which `actual fun` to use, and how do hierarchical/intermediate source sets affect where you can place a single `actual`?
answer
- Binding is per-target, via the source-set tree
- Intermediate sets (nativeMain/appleMain) host shared actuals
- Exactly one actual per leaf target
- Default hierarchy template wires intermediate sets
- Push actual down to leaves when impls diverge
basics
~20 sEach platform build pulls together commonMain plus that platform's source sets. The compiler picks the actual from whichever source set is included for that target. With intermediate source sets like nativeMain, one actual can serve several targets at once.
solid answer
~40 sExpect/actual binding happens **per target compilation**, not globally. When the compiler builds, say, the iOS target, it forms a source-set tree: `commonMain` → an intermediate set like `nativeMain` or `appleMain` → the leaf `iosArm64Main`. The `expect` comes from `commonMain`; the `actual` is resolved from the **most relevant set in that tree** that provides it. This is why hierarchical source sets matter: if several native targets share an implementation (e.g., using `kotlinx.cinterop`/`platform.posix`), you can place a single `actual` in `nativeMain` and all native leaf targets reuse it, instead of duplicating it in `iosArm64Main`, `iosX64Main`, `linuxX64Main`, etc. Each leaf target must still see **exactly one** actual; placing duplicates in both an intermediate and a leaf set causes a conflict. The default hierarchy template wires these intermediate sets automatically in modern Gradle KMP setups.
go deeper
May only know one-actual-per-platform; unaware of intermediate source sets.
Knows you can share an actual in nativeMain but may be vague on the resolution chain.
Explains per-target resolution via the source-set tree and the exactly-one-per-leaf rule.
Designs the source-set hierarchy to maximize reuse and minimize duplicate native actuals across a large target matrix.
## Resolution is per-target, via the source-set tree KMP doesn't resolve expect/actual once. For **each** target (e.g., `iosArm64`, `jvm`, `js`), the compiler assembles the chain of source sets that flow into that target's compilation: the leaf target set, any **intermediate** sets it depends on, up to `commonMain`. The `expect` is read from `commonMain`; the `actual` is the one visible in that chain. ``` commonMain (expect fun random(): Long) ├── jvmMain (actual via java.util.Random) ├── jsMain (actual via Math.random) └── nativeMain (actual via platform.posix) <-- intermediate ├── iosArm64Main ├── iosX64Main └── linuxX64Main ``` ## Why intermediate source sets help Without intermediate sets, every leaf target needs its own `actual`, duplicating identical native code. By placing the `actual` in an **intermediate** set such as `nativeMain` (or `appleMain` for the Apple family), all descendant leaf targets inherit it. This is enabled by the **default hierarchy template** (`applyDefaultHierarchyTemplate()` / automatic in current KMP), which creates `nativeMain`, `appleMain`, `iosMain`, etc., and wires dependencies between them. ## Exactly-one rule still applies per leaf Each leaf target compilation must see **exactly one** actual. If you put an `actual` in both `nativeMain` and `iosArm64Main`, the iOS leaf sees two and fails. Conversely, if an intermediate `actual` can't compile for one specific leaf (because it uses an API absent there), you instead push the `actual` down to the leaf sets that need different code. ## Practical placement strategy - Shared identical impl across all natives → `nativeMain`. - Shared across Apple only → `appleMain`/`iosMain`. - Truly per-target → leaf sets (`iosArm64Main`, `androidMain`, `jvmMain`, `jsMain`, `wasmJsMain`). ## Common pitfalls - A green common build says nothing about whether every leaf has its actual — the check is per target. - Mixing an intermediate actual with a leaf actual for the same expect → "actual declaration has no corresponding expected" / duplicate-actual errors. - Forgetting that `androidMain` and `jvmMain` are distinct leaf sets even though both run on the JVM. ## APIs/keywords involved - `applyDefaultHierarchyTemplate()` (Gradle KMP) for intermediate sets. - `kotlinx.cinterop`, `platform.posix`, `platform.UIKit` for native actuals. - `actual` placed at the granularity that maximizes reuse while keeping one-per-leaf.
- Why might a single actual in `nativeMain` fail for one specific native leaf?If that leaf lacks the API the shared actual uses, it can't compile; you then move the actual into the leaf sets that have differing implementations.
- What enables `nativeMain`/`appleMain` to exist without manual wiring?The default hierarchy template (applyDefaultHierarchyTemplate / automatic in modern KMP) generates and links those intermediate source sets.
Think of source sets as nested folders; the compiler reads the actual from the deepest folder a target can see, and intermediate folders let cousins share one file.
saying these in an interview costs you the question
- Thinking expect/actual resolves globally rather than per target
- Placing the same actual in both an intermediate and a leaf set
- Assuming androidMain and jvmMain share actuals automatically
- Believing a green commonMain build proves all targets compile