Lambdas and list work
Passing a small function into a list call, the single input named it, and the handful of calls that replace most loops you would otherwise write.
A lambda is a small function you do not name
Kotlin lets you pass a function to another function. When that function is short enough that naming it would be noise, you write it in braces: the inputs, an arrow, and the answer it gives back. That block of braces is a lambda. The arrow separates the names on the left from the expression on the right, and the expression’s value is the function’s answer.
A lambda with a single input may leave the name out, because that input has a default name: it. Write it in the body and the lambda takes its one input. When there are two inputs, or when it would be too vague, name them yourself with an arrow before the body. A lambda that names its input is usually easier to read a month later.
List calls that take a lambda
map runs your lambda on every item and collects the answers, so a list of prices becomes a list of doubled prices. filter keeps the items where your lambda answers true. sumOf adds up what the lambda gives back, and count counts the items that pass a test. Each call decides what to do with the lambda’s answer; the lambda only answers for one item.
These are higher-order functions: functions whose input is another function. Reading them is the easy part — the lambda says what happens to one item, and the call says what happens to all of them, one after the other.
Ordering, and chaining the calls
sorted orders numbers or text by their natural order, sortedDescending does the reverse, and sortedBy orders by any value you name with a lambda — the length of a name, for instance. forEach is for when you want to print or act rather than collect. sortedBy keeps equal keys in their original order, which is why a second sort can refine the first.
A chain is a sentence read top to bottom: take the prices, keep the ones over 500, put them in order. Each call hands a new list to the next one, so nothing is modified along the way and the chain can be split across lines to keep it readable. The order of the calls changes how much work happens, even when the final answer is the same.
Reading a chain aloud
A chain is easiest to check when you say it as a sentence with one verb per call. "Take the words, keep the long ones, uppercase them, join with commas" maps directly onto filter, map, map and joinToString. If the sentence needs a detour — "and then, for each one, also..." — that is a sign the chain wants a named function or a loop.
The compiler checks the type at every step, so a chain that does not type-check usually has one call in the wrong place. A filter that returns a list of names cannot be followed by a sumOf over numbers; the error points at the call after the mismatch. Read the chain from the left and ask what type each call hands to the next.
The calls that replace most loops
fun main() {
val prices = listOf(1200, 450, 8900, 300)
val names = listOf("Ada", "Alan", "Grace")
println(prices.map { it * 2 }) // every price doubled
println(prices.filter { it > 500 }) // [1200, 8900]
println(prices.sumOf { it }) // 10850
println(prices.count { it > 500 }) // 2
println(names.sortedBy { it.length }) // by length
println(names.map { it.uppercase() })
println(names.filter { it.length > 3 })
// A lambda stored in a name.
val shout = { word: String -> word + "!" }
println(names.map(shout))
}Asking a list the same question five ways
Run it and read each line as a sentence about the list. Then change the filter to keep prices under 500, add a line that maps the prices to text, and sort the names by the last letter of each name.
A lambda is a value with a function type
The braces are an expression like any other: they produce a function value, and that value has a type such as (Int) -> Int. map accepts a (T) -> R, calls it once per item, and collects the results. Because the lambda is a value, it can be stored in a val, passed to another function, or returned from one — the list calls are not special syntax, just functions that take functions.
Chaining works because each call returns a new list. filter runs first and hands a shorter list to map, so the lambda in map sees only the items that passed. Reverse the order and the lambda in filter sees every item, while map runs on all of them before the filter removes any. The answer can be the same, but the work is not.
Three lambda mistakes are worth catching early. forEach is for side effects and returns Unit, so it cannot replace the map that was building a list. Inside nested lambdas, it refers to the nearest one, which is why naming the input is clearer as soon as a second lambda appears. And a lambda that changes a variable outside itself makes a chain harder to read; collect the answers instead and act once at the end.
Try these next
- Turn a list of prices into a list of labels with
map, then keep only the labels over a certain length. - Use
filterthenmap, and the same calls in the other order, and compare the printed results. - Store a lambda in a
valthat takes a name and returns a greeting, then pass it tomap.
A word report, one call at a time
Run it and read each line as a question about the same list. Then lower the length test to 3, print the words in reverse alphabetical order, and join the transformed words with a comma.
A loop that starts empty and fills another list is almost always a map or a filter in disguise. When you catch yourself writing that shape, stop and ask which of the two calls you meant.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What is a lambda in Kotlin?
