skip to content

Explain the difference between a bound and an unbound function reference, using `String::length` versus `"hello"::length` as examples. How do their types differ?

level: middleimportance: must knowfreq 55%

answer

  1. Unbound: receiver = first param
  2. Bound: receiver captured, zero extra args
  3. Type::member vs instance::member
  4. Bound = KFunction0, Unbound = KFunction1
  5. Bound receiver captured eagerly, like a lambda

basics

~20 s

An unbound reference (String::length) leaves the object out, so you must pass it in as the first argument. A bound reference ("hello"::length) already fixes the object, so you just invoke it with no extra argument.

solid answer

~30 s

With an **unbound** reference `String::length`, the receiver is not supplied at reference-creation time, so it becomes the first parameter: the value behaves like `(String) -> Int` (a `KFunction1<String, Int>`). With a **bound** reference `"hello"::length`, the receiver is captured *now*, so it behaves like `() -> Int` (a `KFunction0<Int>`) and is invoked with no arguments — it always reports `5`. Binding is also how you reference a method on an existing instance: `obj::method` or `this::method`. The bound receiver is evaluated and captured eagerly when the reference expression runs, exactly like capturing a variable in a lambda.

code

kotlin · 13 lines
kotlin
fun main() {
    val unbound: (String) -> Int = String::length
    val bound: () -> Int = "hello"::length

    println(unbound("hi"))   // 2 — receiver passed in
    println(bound())          // 5 — receiver was captured

    // eager capture proof
    var sb = StringBuilder("ab")
    val r = sb::length
    sb = StringBuilder("abcd")
    println(r())              // 2, original instance
}

go deeper

for a junior

Recognizes instance::m already knows its object while Type::m needs one passed in.

for a middle

States the arity/type difference (KFunction0 vs KFunction1) and that the receiver becomes the first param when unbound.

for a senior

Explains eager capture semantics and chooses bound vs unbound deliberately for callbacks vs collection ops.

for a principal

Reasons about capture lifetime/leaks (bound refs holding instances) and API design implications of exposing bound callbacks.

## The core distinction A member function needs a **receiver** — the object it runs on (`this` inside the method). A function reference can either *carry* that receiver (**bound**) or *leave it open* (**unbound**). ### Unbound: `Type::member` The receiver is omitted, so it slides into the parameter list as the **first argument**: ```kotlin val len: (String) -> Int = String::length // KFunction1<String, Int> len("hello") // 5 — you pass the receiver explicitly listOf("a", "bb").map(String::length) // each element is the receiver ``` ### Bound: `instance::member` The receiver is **captured eagerly** when the reference is created, so it disappears from the parameter list: ```kotlin val s = "hello" val len: () -> Int = s::length // KFunction0<Int> len() // 5 — receiver already fixed to "hello" ``` `this::method` binds the current object; `obj::method` binds whatever `obj` evaluates to. ## Type comparison | Reference | Behaves as | Reflection type | |----------------------|----------------|---------------------------| | `String::length` | `(String)->Int`| `KFunction1<String, Int>` | | `"hello"::length` | `()->Int` | `KFunction0<Int>` | The bound form has **one fewer parameter** because the receiver is pre-filled. ## Eager capture The bound receiver is evaluated *once*, at the point the `::` expression runs — just like a captured variable: ```kotlin var x = StringBuilder("ab") val ref = x::length // captures the current StringBuilder instance x = StringBuilder("abcd") ref() // 2 — still the original instance, not the new x ``` ## When each is useful - **Unbound** shines in `map`/`filter` where each element supplies the receiver. - **Bound** is for passing 'this specific object's method' as a callback, e.g. `register(listener::onEvent)`.

  • When is the bound receiver evaluated?
    Eagerly, when the `::` expression is evaluated — the instance is captured once, like a closure variable. Reassigning the source variable afterward does not affect the reference.
  • How do you bind to the current object inside a class?
    Use `this::method`, which captures the enclosing instance as the receiver and produces a zero-receiver-parameter reference.

Unbound is a generic 'measure length' tool you hand any string to; bound is 'measure THIS string's length' with the string already loaded in.

saying these in an interview costs you the question

  • Saying bound and unbound references have the same arity
  • Claiming the bound receiver is re-evaluated on each invocation
  • Thinking `Type::member` automatically picks some default instance
  • Confusing bound references with extension functions
  • Not realizing unbound member references add the receiver as the first parameter

context