Explain the KMP source-set hierarchy and how intermediate source sets like appleMain enable code sharing.
answer
- Source sets form a dependsOn tree
- commonMain root, leaf sets per target
- Intermediate sets (appleMain, nativeMain) share grouped code
- Visibility = intersection of descendants + ancestors
- Default hierarchy template wires it automatically
basics
~10 sSource sets form a tree. commonMain is the root every target uses. Intermediate sets like appleMain group related targets (iOS, macOS) so you can share code among them without duplicating it per target.
solid answer
~40 sA KMP project is a hierarchy of **source sets**. `commonMain` is the root, shared by all targets. Each target has a leaf set (`androidMain`, `iosArm64Main`, `jsMain`). Between them sit **intermediate source sets** like `appleMain`, `nativeMain`, or `iosMain` that group platforms sharing capabilities. Code in an intermediate set sees APIs common to all of its descendants — e.g. `appleMain` can use Foundation/Darwin APIs shared by iOS+macOS. The modern toolchain provides a **default hierarchy template** (`applyDefaultHierarchyTemplate()`, often automatic) that wires these sets for you. A source set 'depends on' its parent and inherits its visible API surface; a child can `actual`-ize an `expect` declared in any ancestor. This avoids copy-pasting platform code across many native targets.
go deeper
Knows commonMain is shared and each platform has its own source set.
Describes intermediate source sets, the dependsOn tree, and how visibility flows.
Uses the default hierarchy template, actualizes expects at intermediate level, and reasons about API intersection visibility.
Designs source-set topology for many targets, minimizing duplication and controlling which platform APIs leak into which layer.
## Source sets as a dependency tree A **source set** is a named group of Kotlin files with its own dependencies and visible API surface. KMP arranges them into a **hierarchy (a tree of `dependsOn` edges)**: ``` commonMain ├── jvmMain (Android/JVM) ├── jsMain └── nativeMain └── appleMain ├── iosMain │ ├── iosArm64Main │ └── iosSimulatorArm64Main └── macosMain ``` - **`commonMain`** — the root; visible to every target; can use only multiplatform APIs. - **Leaf sets** — e.g. `iosArm64Main`, one per concrete target. - **Intermediate source sets** — e.g. `nativeMain`, `appleMain`, `iosMain` — group targets that share capabilities. ## Why intermediate sets matter Without them, code that works on all Apple targets would have to be copy-pasted into `iosArm64Main`, `iosSimulatorArm64Main`, `macosArm64Main`, etc. An intermediate set like **`appleMain`** is compiled once and shared by all Apple descendants, and it can see the **Foundation/Darwin** APIs common to them. Similarly `nativeMain` can use POSIX-style APIs common to all native targets. ## API visibility follows the tree A source set sees: - the **intersection** of APIs available to all of its descendant targets, plus - everything declared in its **ancestor** source sets. So `appleMain` sees `commonMain` + `nativeMain` + the Apple-common platform APIs, but **not** Android or JS APIs. ## expect/actual across the hierarchy An `expect` declared in `commonMain` can be `actual`-ized in an **intermediate** set (e.g. one `actual` in `appleMain` covering all Apple targets) instead of repeating it in each leaf — a big duplication win. ## The default hierarchy template Modern Kotlin auto-applies a **default hierarchy template** (you can call `applyDefaultHierarchyTemplate()` explicitly) that creates `nativeMain`, `appleMain`, `iosMain`, etc. based on the targets you declare. Before this, teams hand-wired `dependsOn` edges. Each `*Main` set has a parallel `*Test` set (`commonTest`, `appleTest`, …) following the same tree. ```kotlin kotlin { androidTarget() iosArm64(); iosSimulatorArm64() // applyDefaultHierarchyTemplate() // usually implicit sourceSets { commonMain.dependencies { /* shared libs */ } // appleMain is created for you and groups iOS targets } } ```
- Why can appleMain call Foundation APIs but commonMain cannot?appleMain's descendants are all Apple targets, so the Foundation API is common to them; commonMain must support every target, so it only sees fully multiplatform APIs.
- Can you actualize an expect once for all Apple targets?Yes — provide the actual in the appleMain intermediate set instead of repeating it in each leaf source set.
Like a family tree: shared traits at the top, branch-specific traits at intermediate nodes, individual quirks at the leaves.
saying these in an interview costs you the question
- Thinking every target needs its own copy of shared native code
- Believing commonMain can see Foundation/Darwin APIs
- Not knowing intermediate source sets exist
- Confusing source sets with Gradle modules
- Forgetting test source sets mirror the same tree