Skip to main content

Kotlin Collection Filtering

Filtering is one of the most common operations performed on collections. Whether you're working with lists, sets, or maps, Kotlin provides a rich set of functions to filter collections efficiently and expressively.

Introduction to Collection Filtering

Filtering is the process of selecting elements from a collection that meet specific criteria. In Kotlin, filtering operations return a new collection containing only the elements that satisfy the given predicate (a condition that evaluates to true or false).

Kotlin's filtering capabilities are part of its functional programming features, making your code more concise and readable compared to traditional imperative approaches.

Basic Filtering with filter()

The most commonly used filtering function in Kotlin is filter(). It creates a new collection containing only elements that satisfy the given predicate.

kotlin
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// Filter even numbers
val evenNumbers = numbers.filter { it % 2 == 0 }

println("Original numbers: $numbers")
println("Even numbers: $evenNumbers")
}

Output:

Original numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Even numbers: [2, 4, 6, 8, 10]

In this example, filter { it % 2 == 0 } creates a new list containing only even numbers from the original list.

Filtering with Negation using filterNot()

When you want to exclude elements that match a certain condition, you can use filterNot():

kotlin
fun main() {
val fruits = listOf("apple", "banana", "cherry", "date", "elderberry")

// Filter fruits that don't start with 'a' or 'b'
val filteredFruits = fruits.filterNot { it.startsWith('a') || it.startsWith('b') }

println("All fruits: $fruits")
println("Filtered fruits: $filteredFruits")
}

Output:

All fruits: [apple, banana, cherry, date, elderberry]
Filtered fruits: [cherry, date, elderberry]

Filtering by Type with filterIsInstance()

When working with collections that contain different types of objects, you can filter by type using filterIsInstance():

kotlin
fun main() {
val mixed = listOf(1, "two", 3.0, "four", 5, 6.0)

// Filter only String elements
val stringElements = mixed.filterIsInstance<String>()

// Filter only Int elements
val intElements = mixed.filterIsInstance<Int>()

println("Original collection: $mixed")
println("String elements: $stringElements")
println("Int elements: $intElements")
}

Output:

Original collection: [1, two, 3.0, four, 5, 6.0]
String elements: [two, four]
Int elements: [1, 5]

Filtering Non-Null Values with filterNotNull()

When dealing with collections that might contain null values, you can filter out those nulls:

kotlin
fun main() {
val withNulls = listOf("apple", null, "banana", null, "cherry")

val nonNullValues = withNulls.filterNotNull()

println("With nulls: $withNulls")
println("Non-null values: $nonNullValues")
}

Output:

With nulls: [apple, null, banana, null, cherry]
Non-null values: [apple, banana, cherry]

Filtering Maps

Kotlin provides specialized filtering functions for maps, allowing you to filter based on keys, values, or both:

kotlin
fun main() {
val scores = mapOf(
"Alice" to 92,
"Bob" to 76,
"Charlie" to 85,
"Diana" to 95,
"Edward" to 68
)

// Filter students with scores above 80
val highScorers = scores.filterValues { it > 80 }

// Filter students whose names start with 'A'
val aStudents = scores.filterKeys { it.startsWith('A') }

// Filter entries based on both key and value
val selectedStudents = scores.filter { (name, score) ->
name.length <= 5 && score >= 80
}

println("All scores: $scores")
println("High scorers: $highScorers")
println("Students with names starting with 'A': $aStudents")
println("Selected students (name <= 5 chars and score >= 80): $selectedStudents")
}

Output:

All scores: {Alice=92, Bob=76, Charlie=85, Diana=95, Edward=68}
High scorers: {Alice=92, Charlie=85, Diana=95}
Students with names starting with 'A': {Alice=92}
Selected students (name <= 5 chars and score >= 80): {Alice=92, Diana=95}

Partitioning Collections

Sometimes you want to divide a collection into two parts based on a condition. The partition() function does exactly this:

kotlin
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

val (evenNumbers, oddNumbers) = numbers.partition { it % 2 == 0 }

println("Original numbers: $numbers")
println("Even numbers: $evenNumbers")
println("Odd numbers: $oddNumbers")
}

Output:

Original numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Even numbers: [2, 4, 6, 8, 10]
Odd numbers: [1, 3, 5, 7, 9]

Filtering with Indices using filterIndexed()

If you need access to the index of each element during filtering, use filterIndexed():

kotlin
fun main() {
val fruits = listOf("apple", "banana", "cherry", "date", "elderberry")

// Keep elements at even indices
val evenIndexFruits = fruits.filterIndexed { index, _ -> index % 2 == 0 }

// Keep elements where index plus length is even
val specialFilter = fruits.filterIndexed { index, value -> (index + value.length) % 2 == 0 }

println("All fruits: $fruits")
println("Fruits at even indices: $evenIndexFruits")
println("Fruits where (index + length) is even: $specialFilter")
}

Output:

All fruits: [apple, banana, cherry, date, elderberry]
Fruits at even indices: [apple, cherry, elderberry]
Fruits where (index + length) is even: [apple, cherry, elderberry]

Real-World Example: Filtering Task List

Let's create a more practical example using a task management system:

kotlin
data class Task(
val id: Int,
val title: String,
val isCompleted: Boolean,
val priority: Priority,
val dueDate: String? = null
)

enum class Priority { LOW, MEDIUM, HIGH }

fun main() {
val tasks = listOf(
Task(1, "Complete Kotlin assignment", false, Priority.HIGH, "2023-10-15"),
Task(2, "Buy groceries", false, Priority.MEDIUM, "2023-10-10"),
Task(3, "Pay utility bills", true, Priority.HIGH, "2023-10-05"),
Task(4, "Call mom", false, Priority.LOW),
Task(5, "Schedule dentist appointment", false, Priority.MEDIUM),
Task(6, "Clean the garage", true, Priority.LOW, "2023-10-20")
)

// Filter incomplete high-priority tasks
val urgentTasks = tasks.filter { it.priority == Priority.HIGH && !it.isCompleted }

// Filter tasks with due dates
val scheduledTasks = tasks.filter { it.dueDate != null }

// Filter completed tasks
val completedTasks = tasks.filter { it.isCompleted }

// Print the tasks
println("All tasks:")
tasks.forEach { println("- ${it.title} (${it.priority}, ${if (it.isCompleted) "Completed" else "Pending"})") }

println("\nUrgent pending tasks:")
urgentTasks.forEach { println("- ${it.title} (due: ${it.dueDate})") }

println("\nScheduled tasks:")
scheduledTasks.forEach { println("- ${it.title} (due: ${it.dueDate})") }

println("\nCompleted tasks:")
completedTasks.forEach { println("- ${it.title}") }

// Count tasks by priority
val tasksByPriority = tasks.groupingBy { it.priority }.eachCount()
println("\nTask count by priority: $tasksByPriority")
}

Output:

All tasks:
- Complete Kotlin assignment (HIGH, Pending)
- Buy groceries (MEDIUM, Pending)
- Pay utility bills (HIGH, Completed)
- Call mom (LOW, Pending)
- Schedule dentist appointment (MEDIUM, Pending)
- Clean the garage (LOW, Completed)

Urgent pending tasks:
- Complete Kotlin assignment (due: 2023-10-15)

Scheduled tasks:
- Complete Kotlin assignment (due: 2023-10-15)
- Buy groceries (due: 2023-10-10)
- Pay utility bills (due: 2023-10-05)
- Clean the garage (due: 2023-10-20)

Completed tasks:
- Pay utility bills
- Clean the garage

Task count by priority: {HIGH=2, MEDIUM=2, LOW=2}

Chaining Filters

One of the strengths of Kotlin's functional approach is the ability to chain operations:

kotlin
fun main() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)

val result = numbers
.filter { it > 5 } // Numbers greater than 5
.filter { it % 2 == 0 } // Even numbers
.filter { it <= 12 } // Numbers not exceeding 12

println("Original numbers: $numbers")
println("Result after chained filters: $result")

// More efficient approach using a single filter
val efficientResult = numbers.filter { it > 5 && it % 2 == 0 && it <= 12 }
println("Result with single filter: $efficientResult")
}

Output:

Original numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
Result after chained filters: [6, 8, 10, 12]
Result with single filter: [6, 8, 10, 12]

While both approaches yield the same result, using a single filter is generally more efficient as it avoids creating intermediate collections.

Performance Considerations

When filtering large collections, consider these performance tips:

  1. Use sequences for large collections: Convert to a sequence before filtering to process elements lazily
kotlin
val result = numbers.asSequence()
.filter { it > 10 }
.filter { it % 2 == 0 }
.toList()
  1. Combine multiple filter conditions into a single operation when possible

  2. Consider using filterTo() to append filtered elements to an existing mutable collection:

kotlin
val evenNumbers = mutableListOf<Int>()
numbers.filterTo(evenNumbers) { it % 2 == 0 }

Summary

Kotlin provides a rich set of filtering functions that make working with collections intuitive and expressive:

  • filter() and filterNot() for basic inclusion/exclusion
  • Type-specific filters like filterIsInstance() and filterNotNull()
  • Map-specific filters: filterKeys(), filterValues(), and filter()
  • partition() for splitting collections
  • filterIndexed() for accessing indices during filtering

These functions follow functional programming principles, producing new collections rather than modifying existing ones, which helps write more predictable and maintainable code.

Exercises

  1. Create a list of integers from 1 to 20 and filter out all numbers that are not prime.
  2. Given a list of strings, filter out all strings that are not palindromes.
  3. Create a list of people (name, age, city) and filter those who are adults (18+) and live in a specific city.
  4. Given a map of employee names to salaries, filter employees who earn above average.
  5. Create a list of mixed data types and use filterIsInstance to extract only the numeric values.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)