1. Overview

The Kotlin language introduces sequences as a way to work with collections. They are quite similar to Java Streams, however, they use different key concepts under-the-hood. In this tutorial, we’ll briefly discuss what sequences are and why we need them.

2. Understanding Sequences

A sequence is a container Sequence<T> with type T. It’s also an interface, including intermediate operations like map() and filter(), as well as terminal operations like count() and find().

Like Streams in Java, Sequences in Kotlin execute lazily. The difference is, if we use a sequence to process a collection using several operations, we won’t get an intermediate result at the end of each step. Thus, we won’t introduce a new collection after processing each step.

It has tremendous potential to boost application performance while working with large collections. On the other hand, there is an overhead to sequences when processing small collections.

3. Creating a Sequence

3.1. From Elements

To create sequence from elements, we just use the sequenceOf() function:

val seqOfElements = sequenceOf("first" ,"second", "third")

3.2. From a Function

To create an infinite sequence, we can call the generateSequence() function:

val seqFromFunction = generateSequence(Instant.now()) {it.plusSeconds(1)}

3.3. From Chunks

We can also create a sequence from chunks with arbitrary length. Let’s see an example using yield(), which takes a single element, and yieldAll(), which takes a collection:

val seqFromChunks = sequence {
    yield(1)
    yieldAll((2..5).toList())
}

It’s worth mentioning here that all chunks produce elements one after another. In other words, if we have an infinite collection generator, we should put it at the end.

3.4. From a Collection

To create a sequence from collections of Iterable interface, we should use the asSequence() function:

val seqFromIterable = (1..10).asSequence()

4. Lazy and Eager Processing

Let’s compare two implementations. The first one, without a sequence, is eager:

val withoutSequence = (1..10).filter{it % 2 == 1}.map { it * 2 }.toList()

And the second, with a sequence, is lazy:

val withSequence = (1..10).asSequence().filter{it % 2 == 1}.map { it * 2 }.toList()

How many intermediate collections were introduced in each case?

In the first example, each operator introduces an intermediate collection. So, all ten elements pass to a map() function. In the second example, there are no intermediate collections introduced, thus map() function has only five elements as input.

5. Conclusion

In this tutorial, we briefly discussed sequences in Kotlin. We’ve seen how to create a sequence in different ways. Also, we’ve seen the difference in processing a collection with sequence and without it.

All code examples are available over on GitHub.

guest
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
JH van Heusden
8 months ago

Hi, I found a disturbing error on the tutorial https://www.baeldung.com/kotlin-string-comparison. Sorry for posting it here, but comments are closed on that page, and it really is a confusing and even ashaming mistake, apparently the code example has never been tested there…. Please have it corrected! It lets you call buildString { “kotlin” } and suggests that that would return “kotlin”, but in fact returns an empty String. Then it demonstrates that is different from “kotlin” (duh…) Worst of all, it makes you believe that this is the case is because it is build separately… But in fact,, the String argument… Read more »

Loredana Crusoveanu
Loredana Crusoveanu
4 months ago
Reply to  JH van Heusden

Thanks for the feedback, we’re updated the article.