skip to content

Source-Set Hierarchy

Source sets are linked with dependsOn so platform sets inherit from common, and the default hierarchy template wires the usual intermediate sets for you. Understanding this graph explains why an API is or is not visible in a given file.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

In a Kotlin Multiplatform project, what does it mean that platform source sets `dependsOn` commonMain, and why is this relationship special compared to a regular library dependency?

level: juniorimportance: must knowfreq 70%

answer

  1. dependsOn = compiler-level edge, not implementation()
  2. Enables expect/actual matching by signature
  3. Directional: platform sees common, never reverse
  4. Transitive up the chain to commonMain
  5. Shares internal visibility + same package

basics

~10 s

Platform code like jvmMain depends on commonMain. This lets shared code declare a feature and each platform fill in the missing parts. It is a special link, not a normal library dependency.

solid answer

~40 s

`dependsOn` is the Kotlin Multiplatform (KMP) edge that builds the source-set hierarchy: a platform source set such as `jvmMain` or `iosMain` `dependsOn` `commonMain`. It is NOT a regular library/`implementation` dependency. The special part is that it enables `expect`/`actual`: `commonMain` declares an `expect fun`/`expect class`, and each leaf source set that `dependsOn` it must supply the matching `actual`. The edge is one-directional (platform sees common, not the reverse) and transitive (an intermediate set like `appleMain` can sit between leaves and `commonMain`). The compiler treats depended-on sources as visible internal code, so `internal` declarations and same-package access work across the edge, which a normal dependency would not allow.

code

kotlin · 8 lines
kotlin
// commonMain
expect fun platformName(): String

// jvmMain (dependsOn commonMain)
actual fun platformName(): String = "JVM " + System.getProperty("java.version")

// iosMain (dependsOn commonMain via the default hierarchy)
actual fun platformName(): String = "iOS"

go deeper

for a junior

Knows platform sets depend on commonMain and that this enables shared code with platform implementations.

for a middle

Explains it is a compiler-level edge enabling expect/actual and shared internal visibility, and that it is directional.

for a senior

Adds transitivity through intermediate sets, contrasts cleanly with Gradle artifact dependencies, and discusses per-target compilation gathering common+platform sources.

for a principal

Reasons about how the edge shapes API visibility/encapsulation strategy across modules and why common must stay agnostic for build/portability guarantees.

## What `dependsOn` is A Kotlin Multiplatform (KMP) project organizes code into **source sets** — named buckets of `.kt` files such as `commonMain`, `jvmMain`, `iosMain`, `androidMain`. The **`dependsOn`** relationship connects them into a directed graph: a more specific (platform) source set `dependsOn` a more general one (ultimately `commonMain`). ```kotlin // build.gradle.kts (manual wiring; usually the template does this for you) kotlin { sourceSets { val commonMain by getting val jvmMain by getting { dependsOn(commonMain) // jvmMain SEES commonMain } } } ``` ## Why it is NOT a normal dependency A normal Gradle dependency (`implementation("group:artifact")` or `api(project(":x"))`) wires **compiled artifacts** together. `dependsOn` wires **source sets at the compiler level**: - **`expect`/`actual` works across it.** `commonMain` may declare `expect fun currentTimeMillis(): Long`. Every leaf platform source set that (transitively) `dependsOn` `commonMain` is REQUIRED to provide an `actual fun currentTimeMillis(): Long`. The compiler matches them by signature. - **Visibility is shared.** `internal` declarations in `commonMain` are visible in `jvmMain`, and both can live in the same package — they compile together as one unit per target. - **It is directional.** `jvmMain` sees `commonMain`; `commonMain` can NEVER see `jvmMain`. Common code must stay platform-agnostic. - **It is transitive.** If `iosArm64Main dependsOn appleMain dependsOn nativeMain dependsOn commonMain`, then `iosArm64Main` sees everything up the chain. ## The hierarchy in practice When the JVM target is compiled, the compiler gathers `commonMain` + `jvmMain` together and resolves each `expect` to its `actual`. When the iOS target is compiled, it gathers `commonMain` + (intermediate sets) + `iosX64Main`. So each **leaf** sees exactly the right `expect`/`actual` set for its platform. ## Key terms - **Source set**: a named group of sources sharing dependencies and a compile target. - **`expect`**: a declaration in shared code with no body — a promise. - **`actual`**: the platform implementation fulfilling that promise. - **Leaf source set**: a platform-specific terminal set (e.g. `iosArm64Main`) that actually compiles to a target.

  • Can commonMain reference a class declared only in jvmMain?
    No. The edge is directional — common code cannot see platform code, so it must use an `expect` declaration (or a common interface) instead.
  • What happens if a leaf source set forgets to provide an actual?
    Compilation fails for that target with an error that the `expect` declaration has no `actual` for the platform.

commonMain writes a job description (expect); each platform source set is a hired worker who must do that exact job (actual).

saying these in an interview costs you the question

  • Calling dependsOn the same as implementation()/api()
  • Thinking commonMain can see platform code
  • Believing expect/actual works without the dependsOn edge
  • Saying the edge is bidirectional
  • Confusing source sets with Gradle modules

context

open as a page

What is the default hierarchy template in Kotlin Multiplatform, and how does it wire intermediate source sets like `nativeMain` and `appleMain` automatically?

level: middleimportance: must knowfreq 60%

basics

~10 s

When you declare targets, Kotlin auto-creates shared groups like nativeMain and appleMain and links them between common and each platform, so you do not have to wire them by hand.

open as a page

Explain how the Kotlin compiler uses the `dependsOn` graph when compiling a single target, and why a single `expect` in commonMain can resolve to different `actual`s for different targets.

level: middleimportance: should knowfreq 40%

basics

~10 s

To build one target, the compiler collects that platform's source set plus everything it depends on up to commonMain, then matches each shared declaration to the platform's own implementation.

open as a page

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%

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.

open as a page

When does the default hierarchy template fall short, and how do you create a custom intermediate source set (e.g. a shared `jvmAndAndroidMain`) while keeping the right expect/actual visibility?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

The default groups only cover common families like Apple or native. When you want to share code between unrelated targets (like JVM and Android), you create your own intermediate source set and link it with dependsOn.

open as a page