1. Introduction
When you’re just starting to program, the most natural reaction to a task like “find an element” or “count something” is to write a loop and do checks inside it. That’s normal: your brain honestly imitates how the processor works. But this approach has downsides: lots of boilerplate code, it’s easy to make a mistake with break, it’s easy to forget the counter’s initial value, and sometimes the code turns into something you’re afraid to touch—even yourself—two days later.
The operations find, any, all, count are standard Kotlin functions for collections that express intent directly: “find”, “check that there is at least one”, “check that all”, “count”. They read almost like plain English, and the code gets shorter. And it also helps separate “we compute” from “we print”, which makes the program more predictable.
There will be lots of examples below. We’ll start with very small lists, and then apply everything to our console mini-app for expense tracking, which we’ve been developing since the lessons about collections and commands.
2. find { ... }: find the first matching element and get comfortable with T?
find is a search operation. You give it a condition (a lambda predicate), and it returns the first element that matches. If there is no matching element, find returns null.
This is an important point: the result type will be nullable (T?), and the Kotlin compiler will require you to handle the “found nothing” case. This isn’t nitpicking—it’s care: the program shouldn’t crash just because the element wasn’t found.
Let’s start with a simple numeric example:
fun main() {
val xs = listOf(1, 3, 4, 6)
val firstEven: Int? = xs.find { it % 2 == 0 }
println(firstEven) // 4
}
Here, firstEven is an Int?. Even if you can see with your own eyes that an even number definitely exists, Kotlin still makes you live by the rules: “it might be null”. This discipline will later save you hours of debugging.
Now an example where there is no such element:
fun main() {
val xs = listOf(1, 3, 5)
val firstEven = xs.find { it % 2 == 0 }
println(firstEven) // null
}
Notice: we didn’t do anything to “return null”. find is designed this way: no matches—then you get null. That’s the contract.
find in a CLI app: find an expense by id
We continue our conditional CLI expense tracker. By agreement (for now, without OOP), we’ll store one expense as Triple(id, category, amount), i.e. Triple<Int, String, Int>. This isn’t an ideal model, but right now collection operations are more important to us than the beauty of the domain model.
Let’s make a function to search for an expense by id using find:
fun findExpenseById(
expenses: List<Triple<Int, String, Int>>,
id: Int
): Triple<Int, String, Int>? {
return expenses.find { expense -> expense.first == id }
}
Now, where we need to delete an expense, we don’t have to run through the list manually.
fun removeExpenseById(
expenses: MutableList<Triple<Int, String, Int>>,
id: Int
): Boolean {
val found = expenses.find { it.first == id } ?: return false
expenses.remove(found)
return true
}
A neat thing happens here: if find returned null, the Elvis operator ?: immediately does return false. It reads almost like a conversation with the program: “if we didn’t find it—immediately report that deletion didn’t work”.
3. any { ... } and all { ... }: “at least one” and “all match” checks
Sometimes we don’t need the element itself. We need a “yes/no” answer: are there elements that satisfy a condition? Or, conversely: do all elements satisfy a condition? Before, you might have introduced a flag var ok = false and changed it in a loop. Kotlin says: “don’t suffer”—there are any and all.
any { predicate } returns true if at least one element matches the condition. all { predicate } returns true if all elements match the condition. These are two operations that love to live in validation and quick “can we proceed?” checks.
Here’s an example:
fun main() {
val words = listOf("a", "bb", "ccc")
println(words.any { it.length == 2 }) // true
println(words.all { it.isNotEmpty() }) // true
}
An important edge case: an empty collection
The most surprising part (and a classic beginner trap): the behavior of any and all on an empty collection.
fun main() {
val empty = emptyList<Int>()
println(empty.any { it > 0 }) // false
println(empty.all { it > 0 }) // true
}
This isn’t a bug and not “a Kotlin quirk”. It’s logic from mathematics:
— any on an empty set is false, because “there isn’t a single element to confirm the condition”.
— all on an empty set is true, because “there isn’t a single counterexample”. Nobody violated the rule—formally, the rule holds.
To make it easier to remember, here’s a small table:
| Collection | Check | Result | Intuition |
|---|---|---|---|
|
|
|
“There’s nobody—so nobody matches” |
|
|
|
“There’s nobody—so nobody violated the condition” |
When does this matter? For example, if you’re checking “all amounts are positive”. If the list is empty, all will say true. Sometimes that’s what you want (an empty list contains no errors). And sometimes you want to separately check “the list is not empty”, but that’s already business logic, not a problem with all.
any/all in a CLI app: quick data checks
Suppose we want to understand whether there are any very large expenses (say, more than 10_000). That’s a pure any:
fun hasBigExpenses(
expenses: List<Triple<Int, String, Int>>
): Boolean {
return expenses.any { it.third > 10_000 }
}
And conversely: we want to check that all amounts are valid (for example, strictly greater than zero). That’s all:
fun allExpensesPositive(
expenses: List<Triple<Int, String, Int>>
): Boolean {
return expenses.all { it.third > 0 }
}
It’s important to keep the meaning in mind: any/all isn’t “kind of like if”, it’s “an answer to a question”. And the more precise your question, the cleaner the code.
4. count { ... }: counting without an external counter
count is an operation that returns the number of elements in a collection. It has two modes.
If you call count() without conditions, you get the collection size (same as size, but sometimes it’s convenient specifically as an “operation” in a chain of reasoning). If you call count { predicate }, you get the number of elements that satisfy the condition. Kotlin documentation classifies count as an aggregating operation: you return a single value based on the contents of the collection.
Mini example:
fun main() {
val xs = listOf(1, 2, 3, 4, 5)
val evenCount = xs.count { it % 2 == 0 }
println(evenCount) // 2
}
Why count { ... } is better than filter { ... }.size
Yes, you can write this:
val evenCount = xs.filter { it % 2 == 0 }.size
It will work. But semantically, you’re doing “filtering” (producing a list) and then taking its size. But you didn’t want a list—you wanted a number. count expresses intent more precisely and reads more simply: “count the elements that match the condition”. Plus, you don’t create an intermediate list (that’s already an efficiency concern, but even without going deep it’s nice to know Kotlin isn’t doing extra work for you).
count in a CLI app: simple statistics
Let’s make a couple of functions that count how many expenses we have in a specific category, and how many total:
fun totalExpenseCount(expenses: List<Triple<Int, String, Int>>): Int {
return expenses.count()
}
fun countByCategory(
expenses: List<Triple<Int, String, Int>>,
category: String
): Int {
return expenses.count { it.second == category }
}
Note: count() returns Int, and count { ... } also returns Int. That means this is a “final result” that you can print, store, compare in an if, and so on.
5. Practical example: remove and stats commands in a CLI
Now let’s implement a “practical” piece of code for our console application: add a delete-by-id command using find, and a stats command that shows several answers using count/any/all. Important: we’re not building a perfect project; we’re practicing collection operations, so the code will be as straightforward as possible and in small chunks.
Printing a single expense
Let’s make a utility to print an expense:
fun printExpense(expense: Triple<Int, String, Int>) {
val id = expense.first
val category = expense.second
val amount = expense.third
println("#$id | $category | $amount")
}
The remove command: “found → removed” without manual iteration
fun handleRemove(
expenses: MutableList<Triple<Int, String, Int>>,
id: Int
) {
val found = expenses.find { it.first == id }
if (found == null) {
println("Expense with id=$id was not found")
return
}
expenses.remove(found)
println("Removed:")
printExpense(found)
}
There are several useful ideas here.
First, find yields Triple<...>?, and we honestly check for null. This is exactly what Kotlin calls “writing code that survives in the real world”.
Second, we remove by object found, not by index. For a MutableList, this is a normal and clear approach: found an element—remove that element.
The stats command: count and check without loops
fun printStats(expenses: List<Triple<Int, String, Int>>) {
val total = expenses.count()
val foodCount = expenses.count { it.second == "food" }
val hasNegative = expenses.any { it.third < 0 }
val allPositive = expenses.all { it.third > 0 }
println("Total expenses: $total")
println("In category food: $foodCount")
println("Has negative amounts: $hasNegative")
println("All amounts are positive: $allPositive")
}
Notice how this code reads: it’s almost like the text of a report. And the nicest part—there’s no need to introduce a var counter, no need to remember break, no need to write four loops in a row.
A useful technique: find a problematic element via find
If allPositive suddenly becomes false, you usually want to understand “what exactly is broken?”. For that, it’s convenient to do a find with the same condition, but inverted:
fun printFirstNegative(expenses: List<Triple<Int, String, Int>>) {
val bad = expenses.find { it.third < 0 }
if (bad != null) {
print("First suspicious expense: ")
printExpense(bad)
}
}
any answers the question “does a problem exist?”, and find helps show “an example of the problem”. This is a very practical combo: validation + diagnostics.
6. Common mistakes when using find/any/all/count
Mistake #1: forgetting that find returns T? and treating the result as non-null.
The most common story looks like this: you write val x = list.find { ... }, and then immediately do println(x.first) (or x.length if it’s a string). The compiler won’t let you do it—and rightly so. You either need to check x != null, or use a safe call x?.first, or define default behavior via the Elvis operator ?:. Once you get used to it, the code becomes more robust, and “random crashes” disappear as a category.
Mistake #2: mixing up find and filter, expecting “all matching” instead of “the first matching”.
find returns one element (the first), not a list. If you need to collect all matching elements, that’s filter style (we discussed that in the previous lecture about map/filter). The mistake usually shows up quietly: you think you found “all big expenses”, but in reality you only found the first one and relaxed. This is one of those bugs that especially like living in reports.
Mistake #3: being surprised by the behavior of all on an empty collection.
If your expenses list is empty, expenses.all { it.third > 0 } will return true. That’s correct by contract, but sometimes it doesn’t match the meaning “we have at least some data”. If it’s crucial for you to distinguish “everything is fine” from “we have no data”, add a separate emptiness check where the business rule is formulated. The key point: all isn’t “wrong”—it honestly answers the question you asked.
Mistake #4: using count where a check is needed, and forgetting about intent.
Sometimes people write something like if (xs.count { it < 0 } > 0) ... This works, but it’s harder to read: first we count, then we compare. If what you mean is “is there at least one negative”, then any { it < 0 } expresses it more simply. count is great when you truly need a number.
Mistake #5: trying to do side effects inside any/all and then being surprised by the logic.
Technically, you can write xs.any { println(it); it < 0 }, but you’ll get a “weird” effect: any stops at the first match and won’t print all elements. That’s not a bug—that’s short-circuiting (operations can finish earlier than traversing the whole list). So printing and other “actions” are better kept separate, while any/all/find/count should be used as pure questions to the data.
Mistake #6: forgetting that count { ... } traverses the entire collection, while find/any/all may stop earlier.
In small educational examples you won’t see the difference, but on large lists it can be noticeable: find/any/all often finish on an early match or violation, while count must count to the end. Even if you’re not thinking about performance yet, it’s useful to understand “why an operation might have been faster”—it helps you write more intentional code.
GO TO FULL VERSION