skip to content

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