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.
answer
- expect object = singleton; expect class = instantiable
- expect class for concrete type/value semantics, app-internal
- interface + expect fun factory = mockable + stable seam
- actual typealias when a platform type already fits
- expect object must be actualized by an object
basics
~20 sAn 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.
solid answer
~50 s`expect object Config` declares a **singleton**: every target provides exactly one `actual object Config`, and common code references the single instance — ideal for platform constants or stateless platform services. `expect class` declares an instantiable type; callers do `Platform()`. Choose `expect class` when shared code needs a **concrete type** (value semantics, identity, fields) rather than just an interface — e.g. a UUID, an atomic, a clock you `new` up. Choose an **interface + `expect fun create(): Api`** when you only need polymorphic behavior: this is more testable (you can supply a fake `Api` in common tests), avoids the expect-class Beta warning, and decouples the contract from a single implementation. Trade-offs: expect class gives zero-overhead direct references and concrete typing but pins you to one implementation per target and the Beta feature; interface+factory adds an allocation/indirection but is mockable and stable. For libraries lean interface+factory or `actual typealias`; for internal app seams expect class/object is fine.
code
kotlin · 8 lines// Singleton seam
expect object BuildConfig { val isDebug: Boolean }
// Behavior seam (stable, mockable)
interface Storage { fun save(key: String, value: String) }
expect fun createStorage(): Storage
// commonTest can inject a fake Storage; it cannot fake an expect class easilygo deeper
Knows expect object is a singleton and expect class can be instantiated.
Explains the per-platform actual object/class matching and a basic use case for each.
Weighs expect class vs interface+factory on testability, stability, and indirection, and brings in actual typealias.
Defines team-wide seam-selection guidance and considers API evolution, binary compatibility, and test architecture across the source-set graph.
## expect object vs expect class - **`expect object`** = a shared **singleton**. Each target supplies one `actual object`: ```kotlin // commonMain expect object BuildConfig { val isDebug: Boolean } // androidMain actual object BuildConfig { actual val isDebug: Boolean = android.os.Debug.isDebuggerConnected() } ``` Use for platform constants, a single stateless service, or a registry. There is exactly one instance; common code references `BuildConfig.isDebug` directly. - **`expect class`** = an instantiable type. Common code can create many instances: `Platform()`, `Uuid("...")`. ## When expect class earns its keep Reach for `expect class` when shared code genuinely needs a **concrete type**, not just behavior: - **Value/identity semantics**: you want `equals`/`hashCode`, fields, or a value-class-like wrapper. - **Avoiding an extra indirection** in hot paths where an interface vtable call matters. - **No suitable platform type to typealias to**. ## When interface + factory wins Model the seam as a common **interface** plus an `expect fun` factory: ```kotlin // commonMain interface Storage { fun save(key: String, value: String) } expect fun createStorage(): Storage ``` Advantages: - **Testability**: common tests can pass a fake `Storage` — you cannot easily fake a concrete expect class. - **Stability**: `expect fun` is stable; you sidestep the expect-class Beta warning. - **Decoupling**: multiple implementations per platform are possible (the factory chooses). Cost: an interface call has indirection, and you allocate an implementation object. ## actual typealias as a third option If a platform type already does the job, `actual typealias` maps the expect onto it without writing a new class — least code, least risk. ## Decision heuristic - Need a singleton of platform data/service -> `expect object`. - Need a concrete instantiable shared type, app-internal -> `expect class`. - Need mockable behavior or a stable public-library seam -> interface + `expect fun` factory. - A platform type already fits -> `actual typealias`. ## Gotchas - Both expect object and expect class emit the Beta warning (they are expected *classifiers*). - An expect object's actual must also be an `object` — you cannot actualize it with a class. - Common tests against an expect class run only when a target is selected, so you can't easily substitute a fake.
- Can you actualize an expect object with a regular class?No. An expected object must be matched by an actual object on every target; the singleton nature is part of the contract.
- Why is interface + factory more testable than an expect class?Common tests can substitute a fake interface implementation, whereas an expect class resolves to a real platform actual only when a target is compiled, leaving no seam to mock from commonTest.
expect object is a single shared utility outlet on every wall; expect class is a power tool you can plug in many of; interface+factory is asking the building to hand you whatever tool fits the socket.
saying these in an interview costs you the question
- Saying expect object can be actualized by a class
- Claiming expect class is always preferable to an interface seam
- Ignoring testability when picking the seam shape
- Forgetting that both classifiers carry the Beta warning
- Recommending expect class for a public library API without considering typealias