skip to content

You write `val f = ::max` but `max` has several overloads and it won't compile. How does Kotlin resolve which function a `::name` reference points to, and how do you disambiguate?

level: middleimportance: should knowfreq 45%

answer

  1. Resolution needs an expected function type
  2. Bare overloaded `::name` = ambiguous
  3. Disambiguate via typed variable or call-site param
  4. Two matches still ambiguous -> use a lambda
  5. References can't supply defaults/varargs

basics

~20 s

A ::name reference must match exactly one function. When the name has several overloads, Kotlin needs the expected type to choose. Give it one by declaring the variable's function type, and it picks the matching overload.

solid answer

~50 s

`::name` is resolved against an **expected function type** from the surrounding context: the parameter type at a call site, or a declared variable type. With overloads, a bare `val f = ::max` has no expected type, so resolution is ambiguous and fails. You disambiguate by supplying the expected type: `val f: (Int, Int) -> Int = ::max` or by passing the reference where the parameter type is known, e.g. `reduce(::max)`. The compiler then selects the single overload whose signature is assignment-compatible with that function type. If two overloads both match the expected type, it's still ambiguous and you must change the type or fall back to a lambda `{ a, b -> max(a, b) }`, which lets normal call-site overload resolution pick. References can't carry default arguments or vararg spreading, so an overload requiring those may not be referenceable directly.

code

kotlin · 12 lines
kotlin
fun parse(x: Int): String = "int:$x"
fun parse(x: String): String = "str:$x"

// Ambiguous without context:
// val f = ::parse                    // compile error

// Disambiguate with an explicit function type:
val g: (Int) -> String = ::parse      // selects parse(Int)
val h: (String) -> String = ::parse   // selects parse(String)

println(g(7))   // int:7
println(h("a")) // str:a

go deeper

for a junior

Recognizes the ambiguity error and that adding a type can fix it.

for a middle

Explains expected-type resolution and the typed-variable / call-site fixes accurately.

for a senior

Articulates the edge cases (two compatible overloads, defaults, varargs) and the lambda fallback rationale.

for a principal

Advises team conventions on when to reference vs lambda and how API design (avoiding clashing overloads) eases callable references.

## The problem A function reference `::name` must denote **exactly one** function. If `name` is overloaded, the compiler cannot guess which one you mean unless the context tells it the **expected function type**. ```kotlin fun handle(x: Int) {} fun handle(x: String) {} val r = ::handle // ERROR: overload resolution ambiguity ``` ## How resolution works Kotlin resolves `::name` using the **expected type** flowing in from context: 1. **Variable declaration type** — declare the function type explicitly. 2. **Call-site parameter type** — pass the reference where the parameter's function type is known. ```kotlin val a: (Int) -> Unit = ::handle // picks handle(Int) val b: (String) -> Unit = ::handle // picks handle(String) listOf(1, 2).forEach(::handle) // forEach expects (Int) -> Unit -> handle(Int) ``` The compiler finds the single overload whose parameter and return types are **assignment-compatible** with the expected function type and binds the reference to it. ## When the expected type still doesn't disambiguate If two overloads are both compatible with the same expected type (rare, e.g. via subtyping), the reference stays ambiguous. Then either narrow the expected type or **fall back to a lambda**: ```kotlin val f: (Int, Int) -> Int = { a, b -> maxOf(a, b) } ``` A lambda body is a normal call, so ordinary **overload resolution** at the call inside the lambda picks the right function from the actual argument types. ## Things references can't do (so a lambda may be required) - **Default arguments**: `::f` references a fixed arity; it can't drop parameters that have defaults. A lambda can call `f()` using defaults. - **Vararg spreading / named args**: not expressible through a bare reference. - **Suspend mismatch**: a reference to a non-suspend function won't satisfy a `suspend` function type unless the target is suspend. ## Practical recipe 1. Try `::name` where the parameter type is known. 2. If ambiguous, add an explicit function type on a variable. 3. If still ambiguous or you need defaults/transforms, use a lambda. ```kotlin fun max(a: Int, b: Int): Int = if (a > b) a else b fun max(a: Double, b: Double): Double = if (a > b) a else b val pickInt: (Int, Int) -> Int = ::max // OK, type disambiguates // val bad = ::max // ERROR: ambiguous ```

  • Why can a lambda succeed where the reference is ambiguous?
    Inside a lambda the call `f(arg)` is resolved using the actual argument types via normal overload resolution, which has more information than a bare reference's expected-type matching.
  • Can `::name` reference a function and rely on its default arguments?
    No. A reference fixes the full parameter list. To use defaults you must call the function inside a lambda with fewer arguments.

saying these in an interview costs you the question

  • Saying the compiler picks an overload 'at random' or 'the first one'
  • Not knowing the expected type drives resolution
  • Claiming references support default arguments
  • Suggesting reflection at runtime to pick the overload
  • Believing a lambda and reference are always interchangeable

context