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?
answer
- JVM sees full JDK API, common sees only expect
- no new type → leakage into platform code
- each target needs its own actual
- structural contract, not behavioral parity
- evolving expect can break the alias
basics
~20 sOn 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.
solid answer
~50 sBecause the alias makes `UUID === java.util.UUID` on JVM, anything in jvmMain (or Java callers) sees the *entire* `java.util.UUID` API — `getMostSignificantBits()`, `version()`, etc. — even though commonMain only sees the narrow `expect` surface. That asymmetry is intentional but can surprise: code written against common compiles everywhere, yet platform code can lean on extra members and accidentally couple to the JDK. Each other target must supply its own actual: Native/JS have no `java.util.UUID`, so you alias to a different type or write an `actual class`, and subtle differences (string formatting, equality, thread-safety) can diverge per platform. Also, an aliased type can't be `expect`-side `sealed`/`data` in ways the platform type doesn't honor, and adding a member to the `expect` later can break the JVM alias if `java.util.UUID` lacks it. So the alias is great for interop but pushes you to keep the `expect` surface minimal and the cross-platform semantics aligned.
code
kotlin · 9 lines// commonMain
expect class UUID { override fun toString(): String }
// jvmMain
actual typealias UUID = java.util.UUID
fun jvmOnly(u: UUID): Long = u.mostSignificantBits // JDK extra, fine here
// commonMain — won't compile: member not in expect surface
// fun common(u: UUID): Long = u.mostSignificantBitsgo deeper
Recognizes JVM UUID is the JDK type and other targets need their own actual.
Explains the visibility split between common and platform code and that each target supplies an actual.
Reasons about leakage, behavioral non-parity, and fragile contract evolution as design trade-offs.
Sets policy: minimal expect surfaces, parity tests per target, alias-vs-wrap decisions framed by long-term portability and API stability.
## The core asymmetry A typealias creates **no new type**, so on JVM `UUID` is *identically* `java.util.UUID`. This produces a deliberate visibility split: - **commonMain** sees only the members the `expect class UUID` declares. - **jvmMain / Java interop** sees the **full** `java.util.UUID` API, because there the name resolves to the real JDK class. ```kotlin // commonMain — only expect members visible fun id(): UUID = UUID.randomUUID() // ok // fun bits(u: UUID) = u.mostSignificantBits // would NOT compile in common // jvmMain — the whole JDK API is available fun bits(u: UUID): Long = u.mostSignificantBits // ok: u IS java.util.UUID ``` ## Consequence 1 — platform leakage Platform code can use members beyond the contract. That's powerful (full interop) but risks coupling logic to JDK-only behavior, undermining portability if that logic later needs to move to common. ## Consequence 2 — every target needs its own actual Native, JS, and Wasm have no `java.util.UUID`. For each you must either alias to a different existing type or write an `actual class`: ```kotlin // nativeMain — no JDK; implement or alias to something else actual class UUID actual constructor(/*...*/) { /* ... */ } ``` Differences in string form, equality, ordering, or thread-safety can diverge across targets unless you deliberately align them. The `expect` is only a *structural* contract — it does **not** guarantee identical runtime behavior. ## Consequence 3 — fragile contract evolution Add a new method to `expect class UUID` and the JVM alias keeps compiling **only if** `java.util.UUID` already has a matching member. Otherwise the alias breaks and you must convert to an `actual class`. So aliases couple your common contract to whatever the platform type happens to expose. ## Consequence 4 — modifiers and identity The aliased type's nature (final/open, `equals`/`hashCode`, serialization) is whatever the platform type defines. You can't make `java.util.UUID` `data` or `sealed` via the alias; common code must not assume features the platform type doesn't provide. ## Design takeaways - Keep the `expect` surface **minimal** — every member is a constraint on all platforms. - Treat behavioral parity (formatting, equality) as something you test per target, not something the alias guarantees. - Prefer aliases for stable, ubiquitous platform types (UUID, BigDecimal); wrap when behavior must be normalized.
- If common code only sees the expect members, why does platform leakage matter?Because platform modules can build logic on JDK-only members, that logic becomes non-portable and can't be lifted into common later without rework.
- Does the `expect`/`actual` mechanism guarantee identical behavior across targets?No. It only guarantees a matching structural surface. Formatting, equality, ordering, and thread-safety can differ; you must test parity yourself.
The expect is a small window; the JVM alias is a glass wall — platform code sees everything, common code only the window pane.
saying these in an interview costs you the question
- Assuming common code can call full JDK UUID API
- Believing expect/actual guarantees identical runtime behavior
- Thinking one JVM alias covers Native/JS automatically
- Ignoring that adding an expect member can break the alias
- Claiming you can make the aliased type `data`/`sealed` via the alias