DEV Community

Cover image for Kotlin Sequences vs Kotlin Collection Functions
Lemuel Ogbunude
Lemuel Ogbunude

Posted on

Kotlin Sequences vs Kotlin Collection Functions

It's good to know the difference between using the Kotlin Collection functions and Kotlin Sequences when working with Collections.

Java has some common Stream API functions like Map(), Filter(), FlatMap() and Sorted() . We need to call the Stream API in order to perform these operations on a Collection in Java, not so in Kotlin.

Here is a Java program to filter through a list, get the even numbers, multiply the even numbers by 4 and then print them out.

//Java
var scores  = Arrays.asList(1, 2, 3, 4, 5);
scores.stream()
        .filter(integer -> integer % 2 == 0)
        .map(integer -> integer * 4)
        .forEach(System.out::println);

Let's try to do that in Kotlin:

//Kotlin
val scores = listOf(1, 2, 3, 4, 5)
scores
    .filter { it % 2 == 0 }
    .map { it * 4 }
    .forEach { println(it) }

If my intention was to write the Kotlin code that would execute just as the Java streams did then the above code is not really correct ☹️

Let's prove that the outputs would be different by adding a little text to tweak it:

Java Sample

//Java
var scores  = Arrays.asList(1, 2, 3, 4, 5);
scores.stream()
        .filter(integer -> {
            System.out.println("Filtering " + integer);
            return integer % 2 == 0;
        })
        .map(integer -> {
            System.out.println("Mapping " + integer);
            return integer * 4;
        })
        .forEach(System.out::println);

Output:

Filtering 1
Filtering 2
Mapping 2
8
Filtering 3
Filtering 4
Mapping 4
16
Filtering 5

Kotlin Sample

//Kotlin
val scores = listOf(1, 2, 3, 4, 5)
scores
    .filter {
        println("Filtering $it")
        it % 2 == 0
    }
    .map {
        println("Mapping $it")
        it * 4
    }
    .forEach { println(it) }

Output:

Filtering 1
Filtering 2
Filtering 3
Filtering 4
Filtering 5
Mapping 2
Mapping 4
8
16

Hmmm 🤔 ... something doesn’t seem right here

Collection Functions

When you call Collection functions on a List in Kotlin, each operation returns a new collection with the desired values.

Each operation in the pipeline works on all the elements in the List before going to the next function.

Example:

//Kotlin
val numbers = listOf(1, 2, 3, 4, 5)

val evenMultipliedNumbers = numbers.map {
    println("Initial Map $it")
    it + 3
}.filter {
    println("Filtering $it")
    it % 2 == 0
}.map {
    println("Mapping $it")
    it * 5
}

evenMultipliedNumbers.forEach { println(it) }

When we run the above code we get the output:

Initial Map 1
Initial Map 2
Initial Map 3
Initial Map 4
Initial Map 5
Filtering 4
Filtering 5
Filtering 6
Filtering 7
Filtering 8
Mapping 4
Mapping 6
Mapping 8
20
30
40

It takes the whole List and runs each element through map() then filter() then map() again.

Kotlin Sequence

Depending on your situation, this might not be the code execution you desire. Kotlin Sequences might be what you need.

When using Kotlin Sequence, the values are evaluated lazily.

Let's tweak the above code a little to use Kotlin Sequence:

//Kotlin
val numbers = listOf(1, 2, 3, 4, 5)

val evenMultipliedNumbers = numbers.asSequence().map {
    println("Initial Map $it")
    it + 3
}.filter {
    println("Filtering $it")
    it % 2 == 0
}.map {
    println("Mapping $it")
    it * 5
}

evenMultipliedNumbers.forEach { println(it) }

All you need to do is to call asSequence() before calling on the operations.

Here is the output:

Initial Map 1
Filtering 4
Mapping 4
20
Initial Map 2
Filtering 5
Initial Map 3
Filtering 6
Mapping 6
30
Initial Map 4
Filtering 7
Initial Map 5
Filtering 8
Mapping 8
40

When using Sequences, each element goes through the entire pipeline of functions before progressing to the next element.

Let's take the first element (which is 1) in the above List as an example.

The Sequence takes 1 and then prints out "Initial Map 1". After that command, it adds 3 to 1.

The next function called is filter() so it executes the first command which prints "Filtering 4" (at this point current value is 4 and no longer 1, 3+1 = 4).

Since 4 is even (4%2==0), it moves on to the map() function. Which prints out "Mapping 4" and then multiplies 4 by five.

The value is now 20, and then the forEach() call immediately prints out the first element received by evenMultipliedNumbers.

Return Value

Each Collection function returns a collection of the same type, a quick example:

//Kotlin
val numbers = listOf(1, 2, 3, 4, 5)
val greaterNums = numbers.filter { it > 3 }
println("Variable value $greaterNums")
println("Class Type: ${greaterNums.javaClass.name}")

Output:

Variable value [4, 5]
Class Type: java.util.ArrayList

The value from the operation was assigned to the variable greaterNums, which was of Type ArrayList!

Let's run the exact same code but with Sequences instead:

//Kotlin
val numbers = listOf(1, 2, 3, 4, 5)
val greaterNums = numbers.asSequence().filter { it > 3 }
println("Variable value $greaterNums")
println("Class Type: ${greaterNums.javaClass.name}")

Output:

Variable value kotlin.sequences.FilteringSequence@4eec7777
Class Type: kotlin.sequences.FilteringSequence

It returns new sequences which could mean a lot more efficiency in terms of memory-consumption because no additional collections are being allocated as the Sequence is being processed.

Should you use Sequences in all cases?

Though Kotlin Sequences look shiny, that might not be the best solution for all cases. Sometimes you might just be fine using the Collection Functions. I would put some great articles below for further reading and research on the Sequences.

Kotlin Sequence Tutorial

Kotlin Sequences: An Illustrated Guide

A video tutorial on Kotlin Sequences: Kotlin Sequences

Latest comments (0)