How do function-type typealiases behave with respect to expansion, overload resolution, error messages, and Java/JVM interop?
answer
- Expanded early — before overload resolution
- Can't overload on or disambiguate by aliases
- Errors may show alias name (cosmetic only)
- Runtime = FunctionN, alias invisible
- Java sees Function1<...>, not the alias
basics
~20 sAn alias is expanded to its underlying function type early, so the compiler treats it exactly like that type for overloads and equality. It has no presence at runtime — from the JVM/Java side you only see the underlying FunctionN type.
solid answer
~40 sA function-type typealias is **expanded** to its underlying type during compilation, before overload resolution and most type checking. So `fun f(h: Handler)` and `fun f(h: (Event)->Unit)` are the *same* signature — declaring both is a conflicting-overload error. Two aliases of the same shape collapse to one type, so they can't disambiguate overloads either. Compiler error messages and IDE hints may show the alias name (Kotlin tries to preserve it for readability) but the underlying type governs semantics. There is **no runtime artifact**: at the bytecode/JVM level the type is the corresponding `kotlin.jvm.functions.FunctionN` interface (e.g. `Function1`), and Java callers see only that — the alias name is invisible to Java, reflection, and `::class`. Aliases also don't survive into generated signatures for SAM/Java interop; they're purely a Kotlin-source convenience.
code
kotlin · 7 linestypealias Handler = (Event) -> Unit
fun on(h: Handler) {}
// fun on(h: (Event) -> Unit) {} // ERROR: conflicting overloads (same type)
// Runtime/Java view of `Handler` is kotlin.jvm.functions.Function1<Event, Unit>;
// reflection never reports the name 'Handler'.go deeper
Understands the alias is replaced by the underlying type and isn't visible at runtime.
Knows you can't overload on aliases and that Java/reflection see FunctionN.
Explains early expansion, its effect on overload resolution, and the diagnostics-name cosmetic behavior.
Designs cross-language APIs accordingly — aliases for internal readability, fun interfaces for nominal/Java-facing contracts — and anticipates interop and resolution pitfalls.
## Expansion happens early The compiler **expands** a typealias to its underlying type during front-end resolution, *before* overload resolution and signature comparison. Consequences: ```kotlin typealias Handler = (Event) -> Unit fun on(h: Handler) {} fun on(h: (Event) -> Unit) {} // ERROR: conflicting overloads — same signature after expansion ``` Likewise, two distinct aliases of the same shape both expand to the same type, so they **cannot disambiguate overloads**: ```kotlin typealias A = (Int) -> Int typealias B = (Int) -> Int fun g(f: A) {} fun g(f: B) {} // ERROR: platform/conflicting declarations — A and B are the same type ``` ## Overload resolution Because aliases are transparent, they play **no role** in choosing between overloads — only the expanded function type (arity, parameter types, return type, `suspend`, receiver) matters. You cannot use an alias name to steer resolution. ## Error messages and tooling Kotlin **tries to preserve the alias name in diagnostics and IDE hints** for readability, so an error may read `expected Handler, found ...` even though semantics are governed by the expanded type. This is cosmetic — never rely on the alias for behavior, only for human reading. ## Runtime / JVM representation Kotlin function types compile to the **`kotlin.jvm.functions.FunctionN`** interfaces (`Function0`, `Function1`, … with an `invoke` method). A function-type typealias has **no runtime representation whatsoever**: - Bytecode shows `Function1<Event, Unit>` (or `Unit`/`kotlin.Unit`), never `Handler`. - **Reflection** (`::class`, `javaClass`) sees `FunctionN`, not the alias. - The alias name does not appear in metadata used by other languages. ## Java interop From **Java**, a Kotlin parameter typed `Handler` appears as `Function1<Event, Unit>` (or `kotlin.jvm.functions.Function1`). Java callers must construct/implement that interface and account for `Unit` returns. The alias is **invisible** to Java. If you want a Java-friendly named callback, prefer a `fun interface` (which generates a real, named SAM type usable as a lambda from Java too). ## Practical implications for API design - Don't try to overload on aliases — it won't compile. - Don't expose aliases as a Java-facing contract; they vanish across the boundary. - Use aliases freely for **internal Kotlin readability**; reach for `fun interface` when the type must be nominal, Java-friendly, or carry members. ```kotlin // Same type after expansion — Java sees Function1<Event, Unit> either way: typealias Handler = (Event) -> Unit fun register(h: Handler) {} // Java: register(event -> Unit.INSTANCE); // works against Function1, not 'Handler' ```
- Can you overload two functions that differ only by alias name?No. Both aliases expand to the same function type, producing a conflicting-overloads error.
- What does Java see when a Kotlin API parameter is typed with a function alias?It sees the underlying kotlin.jvm.functions.FunctionN interface (e.g. Function1<Event, Unit>); the alias name is absent.
- If you need a named, Java-callable callback type, what should you expose?A fun interface — it generates a real named SAM type usable as a lambda from both Kotlin and Java.
saying these in an interview costs you the question
- Thinking you can overload functions distinguished only by alias name
- Believing the alias name exists at runtime or in reflection
- Expecting Java to see the alias name
- Claiming aliases affect overload resolution
- Recommending aliases as a Java-facing API contract