skip to content

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