Understanding the Differences Between switchMap and concatMap
Written on
Chapter 1: Introduction to RxJava Operators
It has been quite a while since my last blog entry, and I'm excited to dive back into it with this topic. RxJava offers a robust library filled with numerous operators. While its power is undeniable, understanding how to utilize these operators effectively is crucial; otherwise, it may lead to unexpected behavior. One must thoroughly comprehend each operator, as time can often blur the finer details. There have been instances when I had to revisit the documentation to recall those critical nuances.
Both concatMap and switchMap serve as operators that transform one Observable into another.
Section 1.1: Understanding concatMap
The concatMap operator performs a concatenation of emissions while ensuring that they are ordered. A key point to note is that it waits for the inner Observable to complete before subscribing to the next one. Questions regarding the differences between these operators frequently arise during interviews. If asked about ordering, simply refer to concatMap. Here’s a straightforward example:
val first = BehaviorSubject.create<String>()
val second = BehaviorSubject.create<String>()
@SuppressLint("CheckResult")
fun main() {
first.concatMap {
mapTo(it)}
.subscribe { println(it) }
first.onNext("1")
second.onNext("a")
first.onNext("2")
second.onNext("b")
}
fun mapTo(firstStr: String): Observable<String> {
return second.map {
firstStr + it}
}
This code will output:
1a
1b
As shown, it never outputs 2 since the inner Observable second never completes. If we modify the code slightly:
val first = BehaviorSubject.create<String>()
val second = Observable.just("a", "b")
@SuppressLint("CheckResult")
fun main() {
first.concatMap {
mapTo(it)}
.subscribe { println(it) }
first.onNext("1")
first.onNext("2")
}
Now it will print:
1a
1b
2a
2b
This time, 2 is printed because the second Observable completes after emitting a and b, allowing the subscription to proceed to the next emission.
Section 1.2: Exploring switchMap
The switchMap operator behaves differently; it unsubscribes from the inner Observable whenever a new item is emitted from the source Observable. Let’s see how this works in practice:
val first = BehaviorSubject.create<String>()
val second = BehaviorSubject.create<String>()
@SuppressLint("CheckResult")
fun main() {
first.switchMap {
mapTo(it)}
.subscribe { println(it) }
first.onNext("1")
second.onNext("a")
first.onNext("2")
second.onNext("b")
}
fun mapTo(firstStr: String): Observable<String> {
return second.map {
firstStr + it}
}
This prints:
1a
2a
2b
Here, when the source Observable emits 2, it unsubscribes from the inner Observable, leading to the outputs of 2a and 2b. Only the latest emission from the inner Observable is retained, discarding earlier ones.
To illustrate this further:
val second = BehaviorSubject.create<String>()
@SuppressLint("CheckResult")
fun main() {
first.switchMap {
mapTo(it)}
.subscribe { println(it) }
first.onNext("1")
second.onNext("a")
second.onNext("b")
first.onNext("2")
second.onNext("c")
}
fun mapTo(firstStr: String): Observable<String> {
return second.map {
firstStr + it}
}
The output will be:
1a
1b
2b
2c
As demonstrated, 1a and 1b are printed, but 2a is missing because a was discarded when 2 was emitted.
Now, let's slightly adjust the scenario:
val first = BehaviorSubject.create<String>()
val second = Observable.just("a", "b")
@SuppressLint("CheckResult")
fun main() {
first.switchMap {
mapTo(it)}
.subscribe { println(it) }
first.onNext("1")
first.onNext("2")
}
This will print:
1a
1b
2a
2b
Finally, adding a delay provides an interesting twist:
val first = BehaviorSubject.create<String>()
val second = Observable.just("a", "b").delay(1, TimeUnit.SECONDS)
@SuppressLint("CheckResult")
fun main() {
first.switchMap {
mapTo(it)}
.subscribe { println(it) }
first.onNext("1")
first.onNext("2")
Thread.sleep(2000)
}
The output will be:
2a
2b
Here, 1 is never printed due to the 1-second delay, which allows 2 to be emitted first. As a result, only 2a and 2b are displayed.
Note: Avoid using Thread.sleep in production code; this is solely for demonstration purposes.
In this post, I aimed to clarify the distinctions and applications of concatMap and switchMap. I’ve presented various use cases to highlight their behavior across different scenarios. I hope this sheds light on their functionalities for you.
Support with a clap if you found this post helpful!
The first video titled "switchMap vs concatMap vs mergeMap ... Oh My!" offers a detailed exploration of these operators with practical examples, helping to solidify your understanding.
The second video, "Map, switchMap, mergeMap, flatMap, concatMap, exhaustMap in RxJS - what is the difference?" delves into the differences among various mapping operators in RxJS, providing insights that can enhance your programming skills.