skip to content

expect / actual Declarations

The mechanism that lets common code declare an API without implementing it, requiring each target to supply the implementation. It is the seam that keeps shared logic honest about what is actually portable.

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

explore

questions

15

In a Kotlin Multiplatform project, what does `actual typealias UUID = java.util.UUID` do, and why would you use it instead of writing an `actual class`?

level: juniorimportance: must knowfreq 55%

answer

  1. typealias = new name, not new type
  2. actual satisfies expect by pointing at JDK type
  3. no wrapper, no delegation, members free
  4. java.util.UUID is the class itself
  5. structural match required

basics

~10 s

It says: on this platform, the shared expect class UUID is exactly the platform's own UUID type. You reuse an existing native type directly instead of writing a wrapper class around it.

solid answer

~40 s

In common code you declare `expect class UUID`. Each platform must supply a matching `actual`. With `actual typealias UUID = java.util.UUID`, the JVM target satisfies the `expect` by pointing the name `UUID` at the already-existing `java.util.UUID`. No new class is generated and no wrapper/delegation is written — at compile time `UUID` simply *is* `java.util.UUID`, so all its constructors, methods (`randomUUID()`, `toString()`), and equality come for free. You use this when a perfectly good platform type already exists and writing an `actual class` would just mean re-declaring or delegating every member by hand. It is the idiomatic way to bridge shared API to JDK/native types like `UUID`, `BigDecimal`, or `AtomicInteger`.

code

kotlin · 11 lines
kotlin
// commonMain
expect class UUID {
    override fun toString(): String
    companion object { fun randomUUID(): UUID }
}

// jvmMain — satisfied with zero wrapper code
actual typealias UUID = java.util.UUID

// commonMain usage works on JVM thanks to the alias
fun newId(): UUID = UUID.randomUUID()

go deeper

for a junior

Knows expect/actual pair the contract with a per-platform implementation and that the alias reuses an existing type.

for a middle

Explains typealias creates no new type and members come for free, and contrasts with actual class.

for a senior

Discusses structural-match requirement, interop benefits, and when an alias is impossible (must wrap).

for a principal

Frames it as an API-design lever for binary/source compatibility and library boundary shaping across targets.

## The problem `expect`/`actual` solves Kotlin Multiplatform (KMP) lets you write **common** code shared across targets (JVM, Native, JS, Wasm). When common code needs something that only exists per-platform, you declare an **`expect`** declaration — a contract with no body — and each target provides a matching **`actual`** implementation. ```kotlin // commonMain expect class UUID { override fun toString(): String companion object { fun randomUUID(): UUID } } ``` ## Two ways to provide the `actual` 1. **`actual class`** — you write a real class (often wrapping/delegating to a platform type). 2. **`actual typealias`** — you alias the `expect` name to an **existing** platform type. ```kotlin // jvmMain actual typealias UUID = java.util.UUID ``` A **typealias** introduces an alternative *name* for an existing type; it creates **no new type**. So after this alias, in JVM code the name `UUID` and `java.util.UUID` are literally interchangeable — same class, same bytecode, same methods, same `equals`/`hashCode`. ## Why prefer the typealias - **Zero wrapper cost.** No boxing, no delegation, no re-implementing members. `java.util.UUID.randomUUID()` already exists, so the companion/members are satisfied automatically. - **Seamless interop.** Java libraries returning `java.util.UUID` plug straight into your common `UUID` API with no conversion. - **Less code to maintain.** An `actual class` forces you to redeclare every member; the alias inherits them. ## What the compiler checks The aliased type must structurally match the `expect`: every member the `expect` declares (methods, properties, constructors, companion members) must already exist on `java.util.UUID` with compatible signatures, else you get an *actualization* error. ## When you can't use it If no single platform type matches the `expect` surface, or you need extra behavior the platform type lacks, fall back to `actual class` (possibly wrapping the platform type).

  • Does `actual typealias` create a new class at runtime?
    No. A typealias is purely a compile-time name; at runtime there is only `java.util.UUID`. No extra class or object is generated.
  • What happens if `java.util.UUID` is missing a method the `expect` declares?
    Compilation fails with an actualization error: the aliased type must provide every member the `expect` declares with a compatible signature.

It's like a nickname: 'UUID' is just another name for the same person java.util.UUID — not a new person who imitates them.

saying these in an interview costs you the question

  • Saying the typealias generates a wrapper class
  • Claiming it copies methods into a new type
  • Thinking it converts/boxes values at runtime
  • Believing typealias creates a distinct, non-interchangeable type

context

open as a page

In Kotlin Multiplatform, what does declaring `expect class Platform` in common code and `actual class Platform` in a platform source set mean? Give a minimal example.

level: juniorimportance: must knowfreq 65%

basics

~20 s

You write a placeholder class in shared code that says 'this exists, here's its shape.' Each platform (Android, iOS) then provides the real version with the same name and members. Shared code uses it without knowing the platform.

open as a page

What are `expect` and `actual` functions in Kotlin Multiplatform, and how do you use them to expose platform-specific behavior through a common API?

level: juniorimportance: must knowfreq 70%

basics

~20 s

In shared code you write expect fun with no body, like a promise. Each platform (Android, iOS, JVM, JS) provides the real actual fun. Common code calls the function without knowing which platform fills it in.

open as a page

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%

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.

open as a page

What are the exact matching rules between an `expect fun` and its `actual fun`? Where do default parameter values and type parameters go?

level: middleimportance: must knowfreq 55%

basics

~20 s

The actual function must have the same name, the same parameters and return type, and at least the same visibility. Default values and type parameters are written only on the expect side, not repeated on actual.

open as a page

What rules must hold for an `actual typealias` to legally satisfy an `expect class`? Give the constraints the compiler enforces.

level: middleimportance: should knowfreq 40%

basics

~20 s

The aliased platform type must already have everything the expect class promises — same members with matching signatures, matching visibility, and a companion if the expect declares one. The alias just renames that existing type.

open as a page

Why does the Kotlin compiler emit a 'Expected classes are in Beta' warning, and what are the practical risks and alternatives when using `expect class`?

level: middleimportance: should knowfreq 35%

basics

~20 s

Expected classes are still marked Beta, so Kotlin warns you their behavior may change in future versions. For stable public APIs, many teams prefer matching a platform type with actual typealias or using interface + factory instead, reserving expect class for app-internal code.

open as a page

When designing a shared `expect class`, how do you decide between satisfying it with an `actual typealias` versus an `actual class`? Walk through the trade-offs.

level: seniorimportance: should knowfreq 28%

basics

~20 s

Use actual typealias when a platform type already matches the contract exactly — it's free and gives perfect interop. Use actual class when you need to add, rename, normalize, or hide members, or no single platform type fits.

open as a page

Your common `expect class UUID` is satisfied on JVM by `actual typealias UUID = java.util.UUID`. What surprising consequences does this aliasing have for common code consumers and for other targets?

level: seniorimportance: should knowfreq 30%

basics

~20 s

On JVM, UUID literally is java.util.UUID, so its full Java API leaks into JVM-side code, and common code must still only use the members the expect declares. Other targets need their own actual, which may behave differently.

open as a page

In a hierarchical KMP project, an `expect class` defined in `commonMain` fails to compile for one target with 'expected declaration has no actual'. Walk through the likely causes and how source-set hierarchy and intermediate source sets affect where the actual may live.

level: seniorimportance: should knowfreq 22%

basics

~20 s

Usually one target has no matching actual. In hierarchical projects the actual can live in an intermediate source set (like appleMain shared by iOS targets), but every leaf target must still be covered. Check package/name match, that all members are actual, and that the source set is on that target's compilation.

open as a page

Compare `expect object` with `expect class`, and explain when you would choose an expect class over an interface-plus-factory seam for a shared API.

level: seniorimportance: should knowfreq 28%

basics

~20 s

An expect object is a single shared singleton with one actual object per platform; an expect class can be instantiated many times. Use expect class when you need a concrete shared type with value/identity semantics; use an interface plus factory when you only need behavior and want a stable, mockable seam.

open as a page

How does the compiler decide which `actual fun` to use, and how do hierarchical/intermediate source sets affect where you can place a single `actual`?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Each platform build pulls together commonMain plus that platform's source sets. The compiler picks the actual from whichever source set is included for that target. With intermediate source sets like nativeMain, one actual can serve several targets at once.

open as a page

When should you choose `expect`/`actual` functions versus a common interface with per-platform implementations? What are the trade-offs, including testability and inlining?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Use expect/actual for small, fixed platform calls where there's exactly one implementation per platform. Use a common interface when you want to swap or mock implementations, inject dependencies, or have more than one variant.

open as a page

A teammate writes `actual typealias Instant = java.time.Instant` to satisfy `expect class Instant`, and it fails to compile. Walk through the likely causes and how to diagnose them.

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Usually the expect class declares a member (method, constructor, or companion factory) that java.time.Instant doesn't expose with a matching signature, or visibility/type parameters don't line up. The fix is to align the expect surface or use an actual class.

open as a page

What constraints and pitfalls apply to `expect`/`actual` functions around `suspend`, return-type variance across platforms, and evolving the API over time?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

If the expect function is suspend, every actual must be suspend too. The common return type must be something all platforms can satisfy. Changing an expect signature forces updating every actual, so design these boundaries carefully.

open as a page