Pocket Studio Academy
HomePart 11.17

Lambdas and higher-order functions

Full course11 min read·4 questions

In Kotlin a function is a value you can store, pass around and hand to something else. This is what those braces you have been giving to filter really are — and it is how every button in Part 3 works.

You have been doing this for two lessons

Look at this again:

Playground.ktkotlin
prices.filter { it >= 7 }

You know what it does. Here is the question we skipped: what, exactly, did you hand to filter?

Not a number. Not a piece of text. You handed over an instruction — a small piece of code that filter then ran, once per item, on your behalf.

That is the idea this lesson is about, and it is one of the big ones. In Kotlin, a function is a value. You can put one in a , pass it as an , and get one back from another function, exactly like a number or a string.

Think of it like this

A teacher has two hundred exam papers to mark and an assistant to help.

The teacher does not mark the papers. The teacher hands over the stack and a slip of paper saying how to mark one: give a point for every correct answer, take one off for a blank.

The assistant does all the walking — pick up paper, apply slip, write score, next paper. The slip does all the deciding.

filter is the assistant. { it >= 7 } is the slip. Neither is much use without the other, and the useful part — the thinking — fits on the slip.

A function with no name

A is a function written inline, with no name, between braces:

Playground.ktkotlin
1val double = { x: Int -> x * 2 }
2println(double(5))

That prints 10. Reading it left to right: the inputs, then an arrow, then what to do.

  • Everything before the -> is the .
  • Everything after it is the body.
  • The last line of the body is the result. There is no return in a lambda.

Once it is in a val, you call it with brackets like any other : double(5).

The type of a function

double has a , just like everything else in Kotlin. Its type is:

Playground.ktkotlin
val double: (Int) -> Int = { it * 2 }

Read (Int) -> Int as "takes an Int, gives back an Int". Once you have written the type on the left, Kotlin knows the parameter is an Int, so you can drop it from the braces — and then becomes available as the automatic name for the single input.

That is the whole rule behind it: one unnamed parameter, and Kotlin calls it it. With two parameters you must name them yourself:

Playground.ktkotlin
1val add = { a: Int, b: Int -> a + b }
2println(add(2, 3))

When a lambda gives nothing back

Playground.ktkotlin
val shout: (String) -> Unit = { println(it.uppercase()) }

is Kotlin's way of writing nothing comes back. println prints and hands back nothing, so this lambda hands back nothing too. You will see () -> Unit constantly in Android: it is the type of "some code to run when something happens".

Higher-order functions

A is one that takes a function as an input. You have used several already — filter, map and sumOf are all higher-order. Now write one:

Playground.ktkotlin
1fun applyTwice(n: Int, f: (Int) -> Int): Int {
2  return f(f(n))
3}

The second is not a number — it is a function. Inside the body, f(n) runs whatever was handed in, and the result goes straight back into f again.

Playground.ktkotlin
println(applyTwice(3, double))

Prints 12: three doubled is six, six doubled is twelve. And because f is just a parameter, the same function does something completely different with a different slip:

Playground.ktkotlin
println(applyTwice(3, { it * it }))

Prints 81: three squared is nine, nine squared is eighty-one.

The trailing lambda rule

That last line has an ugly , { ... }) at the end. Kotlin has a rule to fix it:

If the last argument is a lambda, you may move it outside the round brackets.

Playground.ktkotlin
println(applyTwice(3) { it * it })

Same call, and much easier to read. If the lambda is the only argument, the brackets vanish altogether — which is why you have been writing prices.filter { it >= 7 } all along, with no brackets in sight.

This rule is why so much Kotlin looks like it has custom language keywords in it. It does not. repeat(3) { ... } is an ordinary function call, and so is every Compose layout you will meet in Part 3.

Tip

When a lambda does nothing but pass its input straight to an existing function, you can name that function directly with two colons:

names.forEach(::println)

This is a function reference, and it means exactly the same as names.forEach { println(it) }. Use whichever reads better.

Why this matters for real apps

Almost everything interactive in Android is a function you hand over so that somebody else can run it later. That is called a :

a taste of Part 3kotlin
1Button(onClick = { score = score + 1 }) {
2  Text("Roll")
3}

onClick is a parameter of type () -> Unit. You are not clicking anything now — you are handing Compose a slip of paper that says what to do when someone taps. Compose keeps it and runs it at the right moment.

Once you see that shape, an enormous amount of Android stops looking like magic. It is parameters, all the way down.

Try it in Pocket Studio
  1. Open Pocket Studio, tap Projects, and open your Kotlin practice project.
  2. Tap Editor and open Playground.kt.
  3. Select everything in the file and delete it.
  4. Type the program from the walkthrough above.
  5. Tap Run and read the Output panel.

You should see exactly this:

text
112
281
3tick 1
4tick 2
5tick 3
  1. Now add a line at the end of main: println(applyTwice(10) { it - 1 }).
  2. Run it. You get 8 — ten minus one is nine, nine minus one is eight. You changed what applyTwice does without touching applyTwice at all.
  3. Try breaking it: change that new lambda to { it > 1 } and run. It refuses to compile, because a lambda giving back true or false is not an (Int) -> Int. Put it back.
Error Doctor5 common errors
e: Cannot infer a type for this parameter. Please specify it explicitly.
MeansYou wrote something like val double = { x -> x * 2 }. Standing on its own, nothing tells Kotlin what kind of thing x is.
FixEither name the type in the lambda — { x: Int -> x * 2 } — or declare the type on the left: val double: (Int) -> Int = { it * 2 }. Inside a call like filter, this never happens, because the list already says what the items are.
e: Unresolved reference: it
Meansit only exists when the lambda has exactly one parameter and you have not named it. With two parameters, or once you write x ->, there is no it.
FixName the parameters and use those names: { a, b -> a + b }.
e: Type mismatch: inferred type is (Int) -> Int but (Int) -> Unit was expected
MeansThe function you passed has the wrong shape. This one wanted something that returns nothing, and you gave it something that returns a number.
FixCheck the parameter's declared type and match it. Note that the last line of a lambda is its result, so a stray calculation on the final line can change the type by accident.
e: Function invocation 'f()' expected
MeansYou referred to a function parameter without calling it — return f instead of return f(n). Kotlin can see you meant to run it.
FixAdd the brackets and the argument: return f(n). (Handing the function itself back is legal too, but then the return type must be (Int) -> Int rather than Int.)
e: Too many arguments for public final fun applyTwice(n: Int, f: (Int) -> Int): Int
MeansYou passed the lambda twice — once inside the brackets and once as a trailing lambda, as in applyTwice(3, double) { it * it }.
FixPick one form: applyTwice(3, double) or applyTwice(3) { it * it }. The trailing lambda is the last argument, not an extra one.
Recap
  • A is a function with no name, written in braces: parameters, ->, body. The last line of the body is the result.
  • Functions are values. They have like (Int) -> Int, and can be stored, passed and returned.
  • is the automatic name when a lambda has exactly one unnamed parameter.
  • means nothing is handed back. () -> Unit is the type of "code to run later".
  • A takes a function as a parameter — filter and map are ones you already use, and now you can write your own.
  • The rule moves a final lambda outside the brackets, which is why so much Kotlin looks like braces.
  • Next: extension functions, which let you add a function to a type you did not write, and the five scope functions you will see in every Android codebase.