skip to content

Bound vs Unbound References

A bound reference captures a receiver, while Type::method leaves the receiver as the first parameter, changing the arity of the resulting function type. That arity difference is precisely what interviewers test.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

Given `class Box(val n: Int) { fun scaled(f: Int) = n * f }`, what are the function types of `Box::scaled` and `box::scaled`, and how do you invoke each?

level: middleimportance: must knowfreq 45%

basics

~10 s

Box::scaled needs both the box and the factor, so its type is (Box, Int) -> Int. box::scaled already has the box, so it only needs the factor: (Int) -> Int.

open as a page

Why does `list.filter(String::isEmpty)` work for a `List<String>`, and how does the unbound `::isEmpty`-style reference satisfy `filter`'s expected `(String) -> Boolean` type?

level: middleimportance: should knowfreq 38%

basics

~10 s

String::isEmpty is a function that takes one string and returns true/false. filter calls it once per element, passing each string in. That matches exactly what filter wants.

open as a page

When is the receiver of a bound reference captured, and how do `this::method` and bound references to a `var` behave over time?

level: seniorimportance: should knowfreq 28%

basics

~10 s

The object is captured the moment you write the bound reference, not when you call it. If you later reassign the variable, the reference still points at the original object.

open as a page

When both a bound and an unbound interpretation of a `::method` reference could satisfy the expected type, how does Kotlin resolve it, and where can ambiguity or surprises arise?

level: principalimportance: nice to knowfreq 14%

basics

~20 s

Kotlin uses the expected function type to pick the right meaning. Whether you wrote a type name or an instance, plus how many parameters the target slot has, decides bound vs unbound; sometimes you must spell it out to remove ambiguity.

open as a page