Part of Github Copilot with Kotlin

Kotlin with copilot: Use lambda function to print the full name

Sandy LaneSandy Lane

Video: Kotlin with copilot: Use lambda function to print the full name by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Kotlin: Lambda to Print a Full Name

val printFullName: (String, String) -> Unit = { f, l -> println("$f $l") }. Two-argument lambda that returns nothing — the canonical "side-effect" function.

A small puzzle: store a two-argument print operation as a lambda, then call it. Demos the (args) -> Unit function type for "do something, return nothing."

The Copilot prompt

fun main() {
  // Lambda that takes first name and last name and prints the full name
  val printFullName: (String, String) -> Unit =

Copilot generates:

val printFullName: (String, String) -> Unit = { firstName, lastName ->
  println("Full name: $firstName $lastName")
}

printFullName("John", "Doe")   // Full name: John Doe

Walkthrough

1. The type annotation. (String, String) -> Unit — takes two Strings, returns nothing.

Unit is Kotlin's "no value" type — equivalent to void in Java/C. A function returning Unit doesn't have to write return Unit; it's implicit.

2. The lambda. { firstName, lastName -> println(...) }. Two parameters separated by commas. The arrow -> separates parameters from body.

3. The call. printFullName("John", "Doe") — same syntax as calling any function.

Why use a lambda at all?

For one-off logic, a regular function is clearer:

fun printFullName(firstName: String, lastName: String) {
  println("Full name: $firstName $lastName")
}

Use a lambda when you need to pass the function around — store it in a variable, pass it as an argument, return it from another function.

Lambdas are first-class values

val printers: Map<String, (String, String) -> Unit> = mapOf(
  "casual" to { f, l -> println("Hey $f $l!") },
  "formal" to { f, l -> println("Mr/Ms $l, born $f") },
  "abbreviated" to { f, l -> println("${f.first()}.$l") },
)

printers["casual"]?.invoke("Alice", "Smith")
// "Hey Alice Smith!"

printers["formal"]?.invoke("Alice", "Smith")
// "Mr/Ms Smith, born Alice"

A Map<String, function> lets you select behaviour by key. Useful for dispatch tables — register handlers without a giant when block.

?.invoke(args) is needed when the value is nullable (Map returns null for missing keys). Without nullable: printers["casual"]("Alice", "Smith") works directly.

Lambdas vs function references

fun printFullName(firstName: String, lastName: String) {
  println("Full name: $firstName $lastName")
}

val ref: (String, String) -> Unit = ::printFullName
ref("John", "Doe")

::printFullName produces a function reference — points to the named function. Equivalent to wrapping in { f, l -> printFullName(f, l) } but cheaper (no extra lambda allocation).

When the lambda body is just calling another function, the reference is cleaner.

Type inference

Kotlin can often infer the lambda type without annotation:

val printFullName = { firstName: String, lastName: String ->
  println("Full name: $firstName $lastName")
}

If the parameter types are explicit, the lambda's type is inferred. If they're not, you need the type annotation on the variable:

val printFullName: (String, String) -> Unit = { f, l ->
  println("Full name: $f $l")   // f and l inferred from the type annotation
}

(...) -> Unit vs () -> Unit

val noArgs: () -> Unit = { println("Hi") }
val twoArgs: (String, String) -> Unit = { f, l -> println("$f $l") }

() is the "no parameters" parameter list. (String, String) is two String parameters. The arrow + return type tells you what the function gives back.

Returning Unit explicitly

val explicit: (String, String) -> Unit = { f, l ->
  println(f)
  println(l)
  Unit
}

The trailing Unit is redundant — the last expression's value isn't returned for Unit-typed lambdas. Most code omits it.

Side effects and pure functions

(String, String) -> Unit is the signature of a side-effect function — it returns nothing of value, so its only purpose is to do something (print, write a file, call an API).

Compare to a pure function: (String, String) -> String returns a String, no side effects:

val makeFullName: (String, String) -> String = { f, l -> "$f $l" }
val name = makeFullName("Alice", "Smith")   // "Alice Smith"
println(name)   // separate side-effect step

Splitting "compute" from "print" makes the compute step testable. The Kotlin stdlib's transformation functions (map, filter, reduce) are pure for this reason.

Common mistakes

Calling instead of storing. val x = printFullName("a", "b") calls the function and stores the return (Unit). To store the function: val x = ::printFullName or val x: (String, String) -> Unit = { ... }.

Wrong number of params. (String, String) -> Unit declared, called with one string → compile error. Match the type.

Trying to return a value from a Unit lambda. The body's last expression is discarded. To return a value, change the return type.

Forgetting commas in multi-arg lambda. { firstName lastName -> ... } is a syntax error. Need the comma.

Confusing (String) -> Unit with (Unit) -> String. Reversed signature — input is Unit (no useful info), output is String. Different shape, different use case.

What's next

Episode 27: Lambda to print a single name. Same idea as today, with one parameter. Smaller building block.

Recap

val f: (P1, P2) -> R = { p1, p2 -> body } for storing functions. (...) -> Unit for side-effect functions. Unit is Kotlin's "no value" return type. Function references via ::name for cheaper call-by-reference. Pure functions (-> String) compose better than impure (-> Unit). Lambdas in maps for dispatch tables.

Next episode: single-arg name lambda.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.