When would you choose a `typealias` for a function type versus a `fun interface`? What are the practical differences?
answer
- typealias = structural, same type, no members
- fun interface = nominal, distinct type, named SAM
- SAM conversion may allocate; alias does not
- Interface can add default members/extensions
- Same-shape aliases are interchangeable; fun interfaces are not
basics
~20 sA typealias just renames a function type — any matching lambda fits. A fun interface is a real, distinct type: lambdas convert to it, but it can have a name, members, and is not interchangeable with other function types. Use the interface when you want a real type or members.
solid answer
~50 s`typealias H = (E) -> Unit` is a transparent alias: `H` *is* `(E) -> Unit`, so it is assignment-compatible with every other alias or raw type of the same shape — convenient but with no nominal identity. A `fun interface H { fun handle(e: E) }` (Single Abstract Method interface) creates a **distinct nominal type**: a lambda is converted via SAM conversion, but an `H` is not interchangeable with a `Validator` or a raw `(E)->Unit`. The interface can declare a name for its method, add `default`/non-abstract members, extension functions, and supertypes; it overload-resolves separately. Costs: SAM conversion may allocate a wrapper object (often optimized), whereas a typealias has none. Choose typealias for lightweight readability where you want structural compatibility; choose `fun interface` when you need a real type, named method, extra members, or to avoid accidental cross-assignment.
code
kotlin · 11 linestypealias Encoder = (String) -> String
typealias Decoder = (String) -> String
fun useEncoder(e: Encoder) { /* ... */ }
val dec: Decoder = { it.reversed() }
useEncoder(dec) // COMPILES — both are (String)->String, no protection
fun interface Enc { fun run(s: String): String }
fun interface Dec { fun run(s: String): String }
fun useEnc(e: Enc) {}
// useEnc(decInstanceOfDec) // would NOT compile — distinct nominal typesgo deeper
Knows both name a callback and that lambdas work with each.
Articulates structural (typealias) vs nominal (fun interface) typing and the members/SAM-conversion differences.
Reasons about allocation, overload resolution, accidental cross-assignment, and Java interop when choosing.
Sets a team convention on when public APIs expose aliases vs fun interfaces, balancing ergonomics, evolvability, and nominal safety.
## Two ways to name a callback ```kotlin typealias Validator1<T> = (T) -> Boolean // alias of a function type fun interface Validator2<T> { // SAM interface fun validate(value: T): Boolean } ``` Both let you accept `{ it.isNotBlank() }`-style lambdas, but they differ fundamentally. ## typealias — transparent / structural - **Same type.** `Validator1<String>` *is* `(String) -> Boolean`. The compiler erases the alias. - **Fully interchangeable.** Any function of the same shape (even a differently-named alias) is assignment-compatible. There is no `validate` method name — you invoke it like a function: `v(x)` or `v.invoke(x)`. - **No runtime cost, no wrapper.** - **Cannot** add members, default methods, supertypes, or its own extension functions. - Top-level only. ## fun interface — nominal / SAM A **`fun interface`** (functional interface) has exactly one abstract method (**SAM = Single Abstract Method**). A lambda is converted into an instance via **SAM conversion**. - **Distinct type.** A `Validator2<String>` is *not* assignable to a raw `(String) -> Boolean` or to another fun interface — even with the same signature. This nominal identity prevents accidental cross-wiring. - **Named method** (`validate`) — clearer call sites and reflection. - Can declare **non-abstract members** (concrete `fun`s, `val`s with getters), companion objects, supertypes, and you can write **extension functions** on it. - Participates in overload resolution as its own type. - **Cost:** SAM conversion may allocate a wrapper instance, though the compiler caches/optimizes stateless lambdas; a typealias allocates nothing extra. ```kotlin fun interface Reducer<S, A> { fun reduce(state: S, action: A): S } fun <S, A> store(initial: S, r: Reducer<S, A>) { /* r.reduce(...) */ } store(0) { acc, x: Int -> acc + x } // SAM conversion of the lambda ``` ## Decision guide | Need | Pick | |------|------| | Just a readable name for a callback param | `typealias` | | Structural compatibility with raw lambdas/aliases | `typealias` | | A distinct type that won't accidentally accept other shapes | `fun interface` | | Default methods / extra members / supertypes | `fun interface` | | A named method for clarity or Java interop | `fun interface` | ## Gotcha Because a typealias is structural, two semantically different callbacks with the same shape (e.g. `Encoder = (String)->String` and `Decoder = (String)->String`) are interchangeable — the compiler won't stop you from passing one where the other is expected. A `fun interface` would.
- Does a typealias ever allocate a wrapper object?No. It is erased to the underlying type. A fun interface may allocate during SAM conversion, though the compiler caches/optimizes stateless lambdas.
- Can a fun interface have more than one abstract method?No — exactly one abstract method (SAM). It may have additional non-abstract/default members, but only one abstract one.
- Why might `fun interface` improve type safety over a typealias?It is a distinct nominal type, so two callbacks with the same shape but different meaning can't be accidentally swapped.
saying these in an interview costs you the question
- Saying typealias and fun interface are equivalent
- Claiming a fun interface can have multiple abstract methods
- Not knowing two same-shape typealiases are interchangeable
- Asserting typealias allocates a wrapper
- Thinking you can add members or default methods to a typealias