skip to content

What are the matching rules between an `expect class` and its `actual class`? Cover constructors, members, supertypes, and the case where the actual carries members the expect did not declare.

level: middleimportance: must knowfreq 50%

answer

  1. same package + name; rename needs actual typealias
  2. every member & constructor matched, compatible signature
  3. actual may ADD members/supertypes — invisible to common
  4. defaults only on expect, never on actual
  5. expected member can be actualized by inheritance

basics

~20 s

The actual must have the same name, package, and matching constructors and members the expect declared, each marked actual. The actual is allowed to add extra members and extra supertypes, but those extras are invisible from shared code.

solid answer

~50 s

An `actual class` must match the `expect class` structurally: same fully-qualified name, same type parameters, and for every expected member a corresponding `actual` member with a compatible signature (return type, parameter types, modality, visibility not weaker). Constructors are matched too — an expected constructor needs an `actual constructor`. Supertypes declared in the expect must be present in the actual. The actual **may** add its own members and supertypes beyond what the expect declared — these platform-only additions are simply not visible from `commonMain`, which only sees the expect's surface. A subtle relaxation: an expected member can be 'actualized' by a member **inherited** from a supertype of the actual class, not only by a directly-written `actual` member. Default parameter values must be specified only on the expect side, never duplicated on the actual. Mismatches (missing member, wrong signature, weaker visibility) are compile errors.

code

kotlin · 10 lines
kotlin
// commonMain
expect class Clock {
    fun now(): Long
}

// jvmMain — actual adds an extra method invisible to commonMain
actual class Clock {
    actual fun now(): Long = System.currentTimeMillis()
    fun nanos(): Long = System.nanoTime() // platform-only, not in expect
}

go deeper

for a junior

Knows the actual must match name and members of the expect and be marked actual.

for a middle

States constructor/member/supertype matching, that actuals may add invisible extras, and that defaults live on the expect.

for a senior

Adds actualization-by-inheritance, visibility/modality constraints, and how to expose platform-only API cleanly.

for a principal

Discusses API-surface governance: keeping the expect minimal, avoiding leaking platform members, and migration implications when expanding the expect.

## The contract direction The `expect` defines the **minimum public surface**. The `actual` must satisfy all of it and may exceed it. ## Name & package The actual must share the **same package and class name** as the expect. You cannot rename it; for that you use `actual typealias` (a sibling feature) to point at a differently-named platform type. ## Constructors Every expected constructor needs a matching `actual constructor`: ```kotlin // commonMain expect class Uuid(value: String) { val value: String } // jvmMain actual class Uuid actual constructor(actual val value: String) ``` Note you can combine `actual constructor` with `actual val` on a primary constructor property. ## Members - Each expected function/property maps to an `actual` member with a **compatible signature**: same name, parameter types, return type. - **Visibility** of the actual may not be weaker than the expect (a public expect cannot be matched by a private actual). - **Modality**: an expected open member should be matched appropriately; you cannot, for instance, make an expected non-open member behave incompatibly. - **Default values** for parameters live **only** on the expect. Repeating them on the actual is an error. ## Actualization by inheritance An expected member need not be a hand-written `actual` member — it can be **provided by a supertype** of the actual class: ```kotlin expect class Logger { fun log(m: String) } // platform open class BaseLogger { fun log(m: String) { /* ... */ } } actual class Logger : BaseLogger() // log() inherited, satisfies the expect ``` ## Supertypes Supertypes named in the expect must appear in the actual. The actual can add **extra** supertypes (e.g. a platform interface) — those are invisible from common code. ## Extra members on the actual The actual freely adds platform-only members. From `commonMain` they are unreachable; only `androidMain`/`iosMain` code that references the concrete actual can use them. This is the standard way to expose extra platform API without leaking it into shared code. ## What fails to compile - Missing an expected member or constructor. - Incompatible signature (different return/param types). - Weaker visibility on the actual. - Duplicated default parameter values on the actual. - A supertype required by the expect missing from the actual.

  • Can the actual class declare a parameter default value that the expect omitted?
    No. Default values must be declared only on the expect declaration; putting them on the actual is a compile error.
  • How can shared code use a method that only exists on one platform's actual?
    It cannot from commonMain. Add the method to the expect (so all targets provide it), or call it only from that platform's source set, or expose it via a separate expect function.

saying these in an interview costs you the question

  • Claiming the actual must mirror the expect exactly with no additions allowed
  • Putting default parameter values on the actual
  • Thinking you can give the actual weaker visibility than the expect
  • Believing an inherited member cannot satisfy an expected member
  • Assuming extra actual members are visible from commonMain

context