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.
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.)
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.
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.
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.
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.
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.
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.
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() } // 123Each 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.
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.
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.
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.
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