skip to content

Function References ::

::name turns a declared function into a callable value, so list.map(::transform) replaces list.map { transform(it) }. It is more than style: the reference keeps the signature explicit and avoids an extra lambda.

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

questions

5

What does the `::` operator do when written as `list.map(::println)`, and how is it different from writing `list.map { println(it) }`?

level: juniorimportance: must knowfreq 70%

answer

  1. `::name` = reuse an existing function as a value
  2. Reference vs lambda = forward-unchanged vs transform
  3. Compiles to a FunctionN/invoke object
  4. Expected type picks the overload
  5. `map(::println)` == `map { println(it) }`

basics

~20 s

::println turns an existing function into a value you can pass around. It does the same job as the lambda { println(it) }, just shorter, because you reuse a function that already exists instead of writing a new one.

solid answer

~40 s

`::name` is a function reference: it produces a callable value (a function object) that points at an already-declared function, so you can pass it where a function type is expected. `list.map(::println)` and `list.map { println(it) }` produce the same result; the reference form just avoids writing a wrapping lambda. Under the hood both become instances implementing a `FunctionN` interface (here `Function1<T, Unit>`), invoked via `invoke`. The reference form is preferred when you simply forward each argument unchanged to one function. You switch to a lambda when you need to transform arguments, call multiple statements, reorder/drop parameters, or supply extra captured values. `::println` resolves to the top-level `kotlin.io.println(Any?)` overload that matches the expected function type.

code

kotlin · 13 lines
kotlin
fun shout(s: String) = s.uppercase() + "!"

val words = listOf("hi", "bye")

// Function reference: forward each element to shout
val refResult = words.map(::shout)        // [HI!, BYE!]

// Equivalent lambda
val lamResult = words.map { shout(it) }    // [HI!, BYE!]

// Store the reference in a typed variable
val fn: (String) -> String = ::shout
println(fn("yo"))                          // YO!

go deeper

for a junior

Knows ::name makes a function into a value and equals the simple forwarding lambda.

for a middle

Explains when a reference is cleaner than a lambda and that it compiles to a FunctionN object.

for a senior

Discusses overload resolution by expected type and the forward-unchanged guideline crisply.

for a principal

Frames references as part of Kotlin's function-as-value model and codifies a team style rule for reference-vs-lambda.

## What `::` means The `::` operator creates a **callable reference**. When the right side is a function name, you get a **function reference**: a value that refers to an existing function and can be stored in a variable, passed as an argument, or returned. A **lambda** like `{ println(it) }` is an anonymous function you write inline. A **function reference** like `::println` points at a function that *already exists* somewhere. Both are values of a **function type** (e.g. `(String) -> Unit`). ```kotlin val names = listOf("a", "b", "c") names.map { println(it) } // lambda: brand-new anonymous function names.map(::println) // reference: reuse existing println ``` Both call `println` once per element. The reference form is shorter and signals intent: "forward each element to this function, untouched." ## How it works under the hood Kotlin function types compile to interfaces named `FunctionN` (`Function0`, `Function1`, ... by arity) with a single `invoke` method. `::println` compiles to an object implementing `Function1<String, Unit>` whose `invoke(it)` calls `println(it)`. So `map` sees an ordinary object and calls `.invoke(element)` on it. ## When to prefer a reference vs a lambda Use `::name` when you **just forward arguments unchanged** to a single function: ```kotlin list.map(::transform) // each item -> transform(item) list.forEach(::println) ``` Use a **lambda** when you need to do more than a straight forward: ```kotlin list.map { it.transform() + 1 } // transform the result list.filter { it > 0 && it < 10 } // multiple conditions list.map { transform(it, factor) }// supply an extra argument ``` ## Overload resolution `::println` is ambiguous on its own because there are several `println` overloads. Kotlin uses the **expected type** at the call site to pick the right one. In `names.map(::println)` where `names: List<String>`, the expected parameter type is `(String) -> Unit`, so the compiler selects the `println(Any?)`-compatible overload. If the expected type can't disambiguate, you must help the compiler (assign to a typed variable or use a lambda). ## Key terms - **Callable reference** — value created by `::` referring to a function/property/constructor. - **Function reference** — the function-name case (`::println`). - **Function type** — a type like `(A) -> B`; the kind of value `::name` produces. - **Arity** — number of parameters; determines which `FunctionN` interface is used.

  • Why does `::println` sometimes fail to compile on its own line?
    Because `println` is overloaded and a bare `::println` has no expected type to disambiguate. Provide one by assigning to a typed variable or passing it where the parameter type is known.
  • Is there any runtime performance difference between `::shout` and `{ shout(it) }`?
    Negligible. Both create a function object implementing FunctionN; the reference form may even reuse a singleton for top-level functions. Choose based on readability.

A lambda is writing a fresh note; a function reference is handing over a business card that points at a person who already does the job.

saying these in an interview costs you the question

  • Saying `::` calls the function immediately instead of producing a value
  • Claiming the reference form behaves differently from the equivalent lambda at runtime
  • Not knowing it produces a value of a function type
  • Thinking you can always use `::` even when arguments need transforming
  • Confusing `::name` with `this::name` (bound reference) as the same thing

context

open as a page

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%

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.

open as a page

How do you create a `::name` reference to a top-level function versus a member function of a class, and what does each reference's type look like?

level: middleimportance: should knowfreq 40%

basics

~20 s

A top-level function is just ::name. For a member function you qualify it with the class, like String::length. The class-qualified one needs an instance to run, so its type includes the receiver as a first parameter.

open as a page

Is `xs.map(::process)` ALWAYS substitutable for `xs.map { process(it) }`? Give concrete cases where a function reference cannot replace the lambda, or behaves differently.

level: seniorimportance: should knowfreq 35%

basics

~20 s

No. A reference only works when you forward the argument straight through, unchanged. If you need to transform the input, use defaults, reorder or drop parameters, add captured values, or handle overloads the compiler can't pick, you must use a lambda.

open as a page

On a code review you see a pipeline rewritten from lambdas to function references (`.map(::normalize).filter(::isValid).map(::toDto)`). As a senior/principal, when do you endorse this style and when do you push back?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Endorse references when each step just forwards the element to one well-named function — it reads clearly and reuses logic. Push back when it forces awkward helper functions, hides parameters, or breaks if someone later needs to tweak an argument.

open as a page