Lambdas Workbook (Part 3): Closures

Eleven short exercises on closures — capturing parameters, capturing and mutating a var, returning a stateful lambda, shared state, and capturing a loop variable.

Practice problems for Closures: What a Lambda Remembers. Each takes a minute or two. Every exercise has a runnable editor — fill in the TODO, press Run (the code compiles on JetBrains’ Kotlin server), then click Show answer to check yourself. Nothing here is a trick question, just direct practice of the ideas from the lesson.

capturing

1. Capture a parameter

Write aboveThreshold(numbers: List<Int>, limit: Int) that returns the elements greater than limit — the lambda captures limit.

Try it — edit, then press Run fun main() { fun aboveThreshold(numbers: List<Int>, limit: Int): List<Int> { return numbers // TODO: keep elements greater than the captured limit } println(aboveThreshold(listOf(1, 5, 10, 3), 4)) }
Show answer Hide answer
fun aboveThreshold(numbers: List<Int>, limit: Int) =
    numbers.filter { it > limit }

2. Capture and mutate

Count how many strings in words are blank, using forEach and a captured var counter. (This mutation of a captured variable is exactly what Java forbids.)

Try it — edit, then press Run fun main() { val words = listOf("a", "", " ", "b") var blanks = 0 // TODO: use forEach to count the blank strings, incrementing blanks println(blanks) }
Show answer Hide answer
var blanks = 0
words.forEach { if (it.isBlank()) blanks++ }

3. Sum without sum()

Total nums by capturing and updating a var inside forEach.

Try it — edit, then press Run fun main() { val nums = listOf(1, 2, 3, 4) var total = 0 // TODO: use forEach to add each element to total println(total) }
Show answer Hide answer
var total = 0
nums.forEach { total += it }

stateful lambdas

4. A counter

Write counter(): () -> Int returning a lambda that yields 1, 2, 3, … on successive calls.

Try it — edit, then press Run fun main() { fun counter(): () -> Int { // TODO: capture a var and return a lambda that yields 1, 2, 3, ... return { 0 } } val next = counter() println("" + next() + next() + next()) }
Show answer Hide answer
fun counter(): () -> Int {
    var n = 0
    return { ++n }
}

The returned lambda keeps n alive after counter has returned.

5. A configured multiplier

Write multiplierOf(factor: Int): (Int) -> Int returning a lambda that multiplies its argument by the captured factor.

Try it — edit, then press Run fun main() { fun multiplierOf(factor: Int): (Int) -> Int { return { 0 } // TODO: multiply the argument by the captured factor } val triple = multiplierOf(3) println(triple(10)) }
Show answer Hide answer
fun multiplierOf(factor: Int): (Int) -> Int = { it * factor }

val triple = multiplierOf(3)
triple(10)   // 30

6. A remembered greeting

Write greeterFor(name: String): () -> String returning a lambda that greets the captured name.

Try it — edit, then press Run fun main() { fun greeterFor(name: String): () -> String { return { "Hello, " } // TODO: include the captured name } println(greeterFor("Ada")()) }
Show answer Hide answer
fun greeterFor(name: String): () -> String = { "Hello, $name" }

shared and looped capture

7. Two lambdas, one variable

Declare two lambdas, add and reset, that both operate on the same captured var total.

Try it — edit, then press Run fun main() { var total = 0 val add = { x: Int -> } // TODO: add x to total val reset = { } // TODO: set total back to 0 add(5) add(3) println(total) reset() println(total) }
Show answer Hide answer
var total = 0
val add = { x: Int -> total += x }
val reset = { total = 0 }

Both close over the same total, so changes through one are visible through the other.

8. Capture in a loop

Fill a list with three lambdas, each capturing the loop value i from 1..3, so calling them in turn prints 123.

Try it — edit, then press Run fun main() { val printers = mutableListOf<() -> Unit>() // TODO: for i in 1..3, add a lambda that prints i printers.forEach { it() } }
Show answer Hide answer
val printers = mutableListOf<() -> Unit>()
for (i in 1..3) printers.add { print(i) }
printers.forEach { it() }   // 123

Each iteration’s i is a fresh binding, so every lambda keeps its own.

9. Collect into a captured list

Write a lambda collect that appends its Int argument to a captured MutableList<Int> named seen.

Try it — edit, then press Run fun main() { val seen = mutableListOf<Int>() val collect = { x: Int -> } // TODO: add x to seen collect(1) collect(2) println(seen) }
Show answer Hide answer
val seen = mutableListOf<Int>()
val collect = { x: Int -> seen.add(x) }

collect(1)
collect(2)
seen   // [1, 2]

10. Tally into a captured map

Count word frequencies into a captured MutableMap<String, Int>, using forEach.

Try it — edit, then press Run fun main() { val words = listOf("a", "b", "a", "c", "b", "a") val freq = mutableMapOf<String, Int>() // TODO: use forEach to count each word into freq println(freq) }
Show answer Hide answer
val freq = mutableMapOf<String, Int>()
words.forEach { freq[it] = freq.getOrDefault(it, 0) + 1 }

11. A counter with reset

Write makeCounter(): Pair<() -> Int, () -> Unit> returning two lambdas — next (yields 1, 2, 3, …) and reset (sets it back to 0) — that share one captured var.

Try it — edit, then press Run fun main() { fun makeCounter(): Pair<() -> Int, () -> Unit> { var n = 0 // TODO: next yields 1, 2, 3...; reset sets n back to 0 val next = { 0 } val reset = { } return next to reset } val (next, reset) = makeCounter() println("" + next() + next()) reset() println(next()) }
Show answer Hide answer
fun makeCounter(): Pair<() -> Int, () -> Unit> {
    var n = 0
    val next = { ++n }
    val reset = { n = 0 }
    return next to reset
}

Both returned lambdas close over the same n.


Going deeper: capture that outlives its scope

12. A counter that remembers

Return a lambda that yields 1, then 2, then 3 on successive calls — even though the function creating it has already returned.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun counter() { val next = counter() Assert.assertEquals(1, next()) Assert.assertEquals(2, next()) Assert.assertEquals(3, next()) } } //sampleStart fun counter(): () -> Int { // TODO: return a lambda that increments and returns a captured counter return { 0 } } //sampleEnd
Show answer Hide answer
fun counter(): () -> Int {
    var n = 0
    return { ++n }
}

The lambda captures the variable n (a shared box on the heap), not a snapshot, so it keeps n alive after counter returns and increments the same one each call.

13. Loops capture cleanly

You add a lambda { print(i) } to a list on each pass of for (i in 1..3), then call them all. Does it print 123 or 333? Reveal to check.

Show answer Hide answer

123. Kotlin’s for loop variable is a fresh binding each iteration, so each lambda captures that iteration’s own i — none of the classic “every closure sees the last value” surprise from some other languages.


Back to the lesson, Closures: What a Lambda Remembers, or on to part four: references, returns, and anonymous functions.

Comments