skip to content

What is the difference between a bound reference like `instance::method` and an unbound reference like `Type::method` in Kotlin?

level: juniorimportance: must knowfreq 55%

answer

  1. Bound = receiver captured; unbound = receiver is first param
  2. Unbound arity = bound arity + 1
  3. instance::length is () -> Int; String::length is (String) -> Int
  4. Unbound fits map/filter; bound fits a fixed object callback
  5. :: is the callable-reference operator

basics

~10 s

A bound reference already remembers which object to call the method on, so you just pass arguments. An unbound reference does not; you must pass the object itself as the first argument.

solid answer

~40 s

A bound reference (`instance::method`) captures a specific receiver object at the point you create it. When you invoke it you only supply the method's own parameters. An unbound reference (`Type::method`) captures no receiver; the receiver becomes an extra leading parameter, so calling it requires passing the instance first. This shows up in the function type: for `String::length`-style member access the unbound form is `(String) -> Int`, while a bound `someString::length` would be `() -> Int`. Bound references are handy for passing an existing object's method as a callback; unbound references are ideal for `map`/`filter` over a collection of objects (`list.map(String::uppercase)`), where each element is fed in as the receiver.

code

kotlin · 5 lines
kotlin
val s = "hi"
val bound: () -> Int = s::length        // () -> Int
val unbound: (String) -> Int = String::length  // (String) -> Int
println(bound())            // 2
println(unbound("hello"))   // 5

go deeper

for a junior

Can state that bound captures the object and unbound needs it passed in; recognizes String::uppercase in map.

for a middle

Articulates the arity difference and writes the correct function types for both forms.

for a senior

Explains why the receiver promotes to the first parameter and picks the right form for callbacks vs collection pipelines.

for a principal

Relates this to overload resolution, expected-type inference, and how the compiler picks bound vs unbound when both could fit.

## What a callable reference is A *callable reference* is a value that points to a function, method, property, or constructor, written with the `::` operator. You can store it, pass it as an argument, and invoke it later. The key question for member functions is: *does the reference already know which object (the receiver) to operate on?* ## Bound reference — receiver is captured Writing `instance::method` produces a **bound** reference. The object on the left (`instance`) is captured and remembered. When you call the reference, you pass only the method's declared parameters — the receiver is already fixed. ```kotlin val name = "Kotlin" val getLength: () -> Int = name::length // bound to `name` println(getLength()) // 6, no argument needed val append: (String) -> String = name::plus // bound; plus takes 1 arg println(append("!")) // "Kotlin!" ``` `name::length` has type `() -> Int` because the receiver is already supplied. ## Unbound reference — receiver is a parameter Writing `Type::method` produces an **unbound** reference. No receiver is captured; instead the receiver is promoted to the **first parameter** of the resulting function type. ```kotlin val lengthOf: (String) -> Int = String::length // unbound println(lengthOf("Kotlin")) // 6, must pass the String val isBlank: (String) -> Boolean = String::isBlank println(isBlank(" ")) // true ``` Here `String::length` has type `(String) -> Int` — one more parameter than the bound form. ## Why arity differs by exactly one The receiver always has to come from *somewhere*. A bound reference gets it from the captured object, so it disappears from the parameter list. An unbound reference gets it from the caller, so it appears as a leading parameter. That is why the unbound form has **arity = bound arity + 1**. ## Typical usage with `map` / `filter` Unbound references shine when each collection element should become the receiver: ```kotlin listOf("a", "bb", "").filter(String::isNotEmpty) // [a, bb] listOf("a", "b").map(String::uppercase) // [A, B] ``` `filter` expects `(String) -> Boolean`, which is exactly the unbound arity. A `::isEmpty`-style member reference written as `String::isEmpty` is `(String) -> Boolean`. ## Bound to `this` Inside a class you can write `this::method` (or just `::method` for top-level functions in the same file scope), which is bound to the current instance. ## Summary table - Bound: `obj::m` → receiver captured → type drops the receiver param. - Unbound: `Type::m` → receiver is first param → type gains a leading param.

  • Which form would you pass to `list.map(...)` to uppercase every string, and why?
    The unbound `String::uppercase`, because `map` needs `(String) -> String` and the unbound reference takes each element as its receiver/first argument.
  • What is the function type of `"abc"::get`?
    `(Int) -> Char` — it is bound to the string, so only the index parameter remains.

A bound reference is a pre-addressed envelope (receiver filled in); an unbound reference is a blank envelope where you write the address (receiver) every time you send it.

saying these in an interview costs you the question

  • Saying both forms have the same arity
  • Claiming Type::method captures an instance
  • Thinking instance::method needs you to pass the instance again
  • Confusing `::` with the safe-call `?.` operator
  • Believing unbound references only work for top-level functions

context