skip to content

Source Sets

A KMP project is a graph of source sets, with common at the root and platform sets depending on it, which decides what code can see which APIs. Getting this hierarchy right is most of KMP project setup.

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

explore

questions

20

In a Kotlin Multiplatform project, what is the `commonMain` source set and what kind of code belongs in it?

level: juniorimportance: must knowfreq 70%

answer

  1. Root production source set, compiled per target
  2. Only stdlib + multiplatform-safe deps
  3. No java.*/android.*/platform.* directly
  4. expect/actual or interfaces for platform code
  5. src/commonMain/kotlin

basics

~10 s

commonMain holds shared code that compiles to every target (JVM, iOS, JS, etc.). Put platform-independent logic there. It can only use APIs available on all targets.

solid answer

~40 s

`commonMain` is the root production source set of a Kotlin Multiplatform module. Code placed there is compiled separately for every configured target, so it must only reference APIs and libraries available on all of them — the Kotlin standard library plus multiplatform-safe dependencies. You write shared business logic, data models, networking/serialization wiring, and `expect` declarations here. Platform-specific pieces live in target source sets like `jvmMain` or `iosMain` (often as `actual` implementations). Because `commonMain` cannot see `java.*`, `android.*`, or `platform.Foundation.*`, anything platform-bound must be abstracted behind an interface or an `expect`/`actual` pair. It is configured in `build.gradle.kts` via the `kotlin { sourceSets { commonMain { ... } } }` block.

code

kotlin · 6 lines
kotlin
// src/commonMain/kotlin/Greeting.kt
expect fun platformName(): String

class Greeting {
    fun greet(): String = "Hello from ${platformName()}"
}

go deeper

for a junior

Knows commonMain holds shared code and only multiplatform APIs are allowed.

for a middle

Explains compilation per target and the expect/actual escape hatch with correct directory layout.

for a senior

Discusses dependency safety (artifacts must exist for every target) and abstraction strategies for platform code.

for a principal

Frames common-code design as an architecture decision: maximizing shared surface while isolating platform concerns behind stable interfaces.

## What `commonMain` is A Kotlin Multiplatform (KMP) module organizes code into **source sets**. `commonMain` is the root *production* source set: every Kotlin file under `src/commonMain/kotlin` is compiled **once per target** that the module declares (e.g. `jvm()`, `iosArm64()`, `js()`, `linuxX64()`). The same source therefore produces a JVM `.class`, an iOS Kotlin/Native binary, a JS module, and so on. ## The hard constraint: lowest common denominator Because that one file must compile for *all* targets, `commonMain` may only reference: - The **Kotlin standard library** (`kotlin.*`, `kotlin.collections.*`, `kotlin.time.*`, `kotlin.coroutines` if added, etc.) — these are multiplatform. - **Multiplatform-safe libraries** — ones that publish artifacts for every target you use (kotlinx.coroutines, kotlinx.serialization, Ktor client, etc.). It **cannot** reference platform-only APIs: no `java.io.File`, no `android.content.Context`, no `platform.UIKit.*`. Those only exist in their respective target source sets. ## Where platform-specific code goes Two escape hatches let common code reach platform APIs: - **Interfaces / abstractions** — declare an interface in `commonMain`, implement it per platform. - **`expect`/`actual`** — declare `expect fun currentTimeMillis(): Long` in `commonMain`; provide an `actual` in each target source set. ```kotlin // src/commonMain/kotlin/Platform.kt expect fun platformName(): String class Greeting { fun greet(): String = "Hello, ${platformName()}!" } ``` ```kotlin // src/jvmMain/kotlin/Platform.kt actual fun platformName(): String = "JVM ${System.getProperty("java.version")}" ``` ## Gradle configuration `commonMain` and its dependencies are declared in the `kotlin {}` DSL: ```kotlin kotlin { jvm() iosArm64() sourceSets { commonMain.dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") } } } ``` ## Key takeaways - `commonMain` = shared, compiled-to-every-target code. - Only stdlib + multiplatform-safe deps allowed. - Reach platform APIs via interfaces or `expect`/`actual`, never directly.

  • Why can't you use `java.io.File` directly in `commonMain`?
    `java.io.File` is part of the JVM/Android platform only; iOS, JS, and Native targets have no JVM, so the code wouldn't compile for them. Common code is limited to APIs present on every target.
  • How does common code call platform-specific functionality?
    Via an `expect` declaration with per-target `actual` implementations, or by declaring an interface in common and injecting a platform implementation.

commonMain is like writing in a language every team member speaks — you can only use words (APIs) everyone understands; specialized jargon goes in per-team handbooks (target source sets).

saying these in an interview costs you the question

  • Thinking `commonMain` can use Java standard library classes
  • Believing common code is compiled only once for all targets
  • Confusing `commonMain` with a separate compiled JAR shared at runtime
  • Not knowing about `expect`/`actual` as the escape hatch
  • Claiming any Maven dependency can be added to `commonMain`

context

open as a page

In a Kotlin Multiplatform project, what is an intermediate source set such as iosMain, and why would you create one?

level: juniorimportance: must knowfreq 60%

basics

~20 s

It is a source set shared by some targets but not all. For example iosMain is shared by iosArm64 and iosX64, so you write code once for both iOS targets instead of copying it into each.

open as a page

In a Kotlin Multiplatform Gradle build, where do you declare a dependency on a library that works on every target, and where do you declare one that only exists on the JVM?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Put shared dependencies in commonMain, inside the kotlin sourceSets block. Put a JVM-only library in jvmMain. Each source set only sees the libraries declared for it or its parents.

open as a page

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%

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.

open as a page

How do you write shared tests for a KMP module, and what role does `commonTest` and the `kotlin.test` library play?

level: middleimportance: must knowfreq 55%

basics

~10 s

Put shared tests in commonTest and use the kotlin.test library. Those tests run once per target, so a single test verifies behavior on JVM, iOS, JS, etc.

open as a page

How does dependsOn create an intermediate source set, and how does it differ from a regular Gradle dependency declaration?

level: middleimportance: must knowfreq 50%

basics

~20 s

dependsOn links one source set to another so it inherits its code and can see its expect declarations. A normal dependency just adds a library on the classpath; dependsOn builds the multiplatform source-set hierarchy itself.

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

What makes a dependency "multiplatform-safe" so it can be added to `commonMain`, and what happens if you add one that isn't?

level: middleimportance: should knowfreq 45%

basics

~10 s

A multiplatform-safe library publishes versions for every target your module uses. If you add a JVM-only library to commonMain, the build fails for non-JVM targets because there's nothing to compile against.

open as a page

What does applyDefaultHierarchyTemplate() do, and when do you still need to define intermediate source sets manually?

level: middleimportance: should knowfreq 45%

basics

~20 s

It auto-creates the standard tree of intermediate source sets (like nativeMain, appleMain, iosMain) and wires them with dependsOn, based on the targets you declared. You only hand-write a set when you need a non-standard grouping the template does not provide.

open as a page

Inside commonMain.dependencies { }, when would you choose api(...) over implementation(...), and how does the choice propagate across targets and to consumers of your KMP library?

level: middleimportance: should knowfreq 40%

basics

~20 s

Use api when types from the dependency appear in your public API so consumers need it too. Use implementation when the dependency is internal. The choice works the same per source set as in plain Gradle.

open as a page

Show the different ways to access a source set (e.g. jvmMain) inside kotlin { sourceSets { } } to declare its dependencies, and explain when each is needed.

level: middleimportance: should knowfreq 55%

basics

~10 s

Modern Kotlin gives you typed accessors like jvmMain.dependencies { }. Older code uses val jvmMain by getting { } or getByName("jvmMain"). You use getting/getByName for custom or not-yet-created source sets.

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

How do `expect` declarations in `commonMain` relate to `actual` implementations, and what are the rules and pitfalls?

level: seniorimportance: should knowfreq 40%

basics

~10 s

In commonMain you write expect declarations — promises of an API. Each target supplies a matching actual. The compiler checks every target provides one; common code calls the expected API without knowing the platform.

open as a page

Why can code in iosMain call POSIX or Apple Foundation APIs while commonMain cannot, and how does the hierarchy determine API visibility?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A source set can only use APIs that exist on every target below it. commonMain must compile for all targets, so it sees only the truly universal API. iosMain compiles only for iOS targets, so it can use APIs all iOS targets share, like POSIX or Foundation.

open as a page

You introduce an intermediate source set appleMain shared by iosArm64Main and macosArm64Main. How do per-target dependency declarations and visibility behave for it, and what does dependsOn change?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Dependencies you declare on appleMain are inherited by every platform source set that depends on it (iosArm64Main, macosArm64Main). dependsOn wires that inheritance and lets appleMain use Apple-specific APIs shared by those targets.

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

Walk through designing a custom jvmAndAndroidMain intermediate source set: when is it worth it, how do you wire it, and what are the pitfalls?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Create it when JVM and Android share a lot of java.*-based code you don't want to duplicate. Wire it with dependsOn so jvmMain and androidMain both inherit from it, and it depends on commonMain. Watch out for Android-only APIs leaking in and template conflicts.

open as a page

In a KMP module that includes androidTarget(), how do you correctly add Android-only dependencies and Android instrumented/unit test dependencies, and what's distinctive about the Android source sets compared to other platforms?

level: seniorimportance: nice to knowfreq 28%

basics

~10 s

Add Android libraries in androidMain.dependencies { }. Android has extra test source sets like androidUnitTest and androidInstrumentedTest. Android also brings build-variant source sets, unlike other KMP targets.

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

When designing a KMP library, how do you decide what belongs in `commonMain` versus platform source sets, and what are the trade-offs of pushing more code into common?

level: principalimportance: nice to knowfreq 25%

basics

~10 s

Put as much platform-independent logic in commonMain as possible to maximize reuse and shared tests, but keep platform APIs out. Over-sharing forces awkward abstractions; under-sharing duplicates logic.

open as a page