skip to content

Explain commonMain and commonTest source sets in KMP and how platform source sets relate to them.

level: middleimportance: must knowfreq 65%

answer

  1. commonMain compiled for ALL targets
  2. platform sets dependsOn(common)
  3. dependsOn = source merge, not implementation dep
  4. visibility flows downward only
  5. intermediate sets: nativeMain / appleMain

basics

~10 s

commonMain holds code shared by every target; commonTest holds shared tests. Platform source sets like jvmMain depend on commonMain, so they see common code and add platform-specific code on top.

solid answer

~40 s

`commonMain` is the source set whose code is compiled for **all** declared targets, so it may use only the Kotlin common standard library and multiplatform-capable dependencies. `commonTest` is its test counterpart, typically using `kotlin.test`. Every platform source set (`jvmMain`, `jsMain`, `iosArm64Main`, …) automatically `dependsOn` the corresponding common set, meaning platform code can reference common declarations and supply platform-only implementations (including `actual`s for `expect`s). The `dependsOn` relation is the multiplatform metaphor — it is not a regular Gradle `implementation` dependency; it merges source sets so the compiler resolves `expect`/`actual` together. With the default hierarchy template, intermediate sets like `nativeMain` or `appleMain` sit between common and the leaf targets, letting you share code across a subset of platforms.

code

kotlin · 13 lines
kotlin
kotlin {
    jvm()
    iosArm64()
    sourceSets {
        commonMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
        }
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
        // jvmMain / iosArm64Main already dependsOn(commonMain)
    }
}

go deeper

for a junior

Knows commonMain is shared code and platform sets add platform-specific code.

for a middle

Explains the dependsOn source-merge edge, downward visibility, and the common-stdlib-only constraint.

for a senior

Uses intermediate source sets deliberately to share across platform subsets and reasons about expect/actual resolution within merged sets.

for a principal

Designs a source-set hierarchy that minimizes duplication and platform leakage across a large target matrix and many modules.

## The shared core: commonMain / commonTest `commonMain` is the **shared** source set. Its code is compiled separately for every target you declared, so it must be platform-agnostic: it can use the Kotlin **common standard library** and any dependency published with multiplatform metadata, but it cannot call JVM-only APIs (e.g. `java.io.File`) or platform-specific functions directly. `commonTest` is the same idea for tests and normally depends on the multiplatform `kotlin("test")` library. ## How platform sets attach: dependsOn When you declare `jvm()`, the plugin creates `jvmMain` and makes it **`dependsOn(commonMain)`**. This `dependsOn` edge is special to KMP: - It is **not** a Gradle `implementation`/`api` dependency. It merges the upstream source set's sources and declarations into the downstream compilation. - Because of the merge, the compiler can match an `expect` in `commonMain` with its `actual` in `jvmMain` in the same compilation. ```kotlin kotlin { jvm(); iosArm64() sourceSets { commonMain.dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") } // jvmMain dependsOn(commonMain) is set up automatically } } ``` ## Visibility direction Code flows **downward**: `jvmMain` sees everything in `commonMain`, but `commonMain` cannot see `jvmMain`. That is why shared abstractions live in common and concrete platform code lives in platform sets. ## Intermediate (hierarchical) source sets The **default hierarchy template** inserts intermediate source sets automatically — e.g. `nativeMain` (shared by all Kotlin/Native targets) and `appleMain` (shared by iOS/macOS). They let you share code across a *group* of platforms without duplicating it in every leaf set. Each intermediate set `dependsOn` common, and the leaf targets `dependsOn` the intermediate. ## Tests `commonTest` houses tests that must pass on every platform; platform test sets (`jvmTest`, etc.) add platform-specific tests and `dependsOn(commonTest)` plus the corresponding main set. ## Key takeaways - commonMain = compiled for all targets, platform-agnostic only. - Platform sets `dependsOn` common (a source-merge edge, not a library dep). - Visibility is downward; intermediate sets share across platform subsets.

  • Can commonMain call java.io.File?
    No. commonMain compiles for all targets including non-JVM ones, so it may only use the common stdlib and multiplatform-capable APIs.
  • Is dependsOn the same as implementation(project(...))?
    No. dependsOn merges source sets within KMP so expect/actual resolve together; implementation is a binary/Gradle dependency.

commonMain is the company-wide policy; each branch office (jvmMain, iosMain) inherits it and adds local rules — but headquarters never sees a single branch's local rules.

saying these in an interview costs you the question

  • Calling dependsOn a normal Gradle implementation dependency
  • Claiming commonMain can use JVM-only APIs
  • Saying commonMain can see code in jvmMain
  • Not knowing intermediate source sets exist for platform subsets

context