RxJS mergeMap vs switchMap vs concatMap vs exhaustMap
Tags: rxjs, angular, webdev
Published: 2023-12-01 07:00:00

First of all, I would like to start off by saying that this isn't the first blog post about this subject. But what I miss the most in other posts is visualizing these operators. This is the reason I created this blog post. So off we go!

`mergeMap`, `switchMap`, `concatMap`, and `exhaustMap` are all called higher-order mapping operators. Instead of mapping values to other values, they map them to Observables, on which you can pipe further.

One of the most used ones is probably the `switchMap` operator. Most commonly, it is used together with HTTP GET calls, something like this:

```TypeScript
this.idParam$.pipe(
  switchMap((id: number) =>   
    this.productsService.getProduct(id)
  )
);
```

People know this operator because of its cancellation effect. But there's also the `mergeMap`, `concatMap` and even `exhaustMap` operators, of which many people are not aware of or don't know when to use them. Let's go through them one by one.

> Note: In each example image down below, you can see "SOURCE" and "INNER". "SOURCE" means the source Observable (e.g., a click event, ActivatedRoute query params, etc.), and "INNER" is the inner Observable that is returned by one of the higher-order mapping operators.

## MergeMap

The `mergeMap` operator is the easiest of them all. It will map each source value to an Observable, which is merged into the output Observable. Simply explained: whatever comes in, comes out, regardless of order.

![mergeMap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/larajwr99yeric3yrtbq.gif)

In this example, you see that when the source (`A`) gets called, the inner source starts emitting its values. When the source gets called again while the inner source is still emitting its values, the new values will be emitted as well. So if the source is called multiple times, all inner sources will emit their values in parallel.

### Use case

A good use case of the `mergeMap` is a list of items in a shopping cart. You have an overview of all items, and next to each item is a button to delete it from the cart. If you're in a hurry, you can delete several items at once by clicking each "delete" button quickly. As a customer, you expect that each item that you click to delete will effectively be deleted. And that is what the mergeMap will do; it will continue to delete each one, even if you already clicked one for deletion.

Now imagine you use `switchMap` instead: you hastily click each "delete" button. Since the first delete is still ongoing on the server while you click the second delete, the first delete will be cancelled, but while the second delete is ongoing, the third is clicked for deletion, canceling the second one, etc. This makes deleting items in your shopping cart quite unpredictable.

## SwitchMap

`switchMap` will map each source value to an Observable, which is merged into the output Observable, but also cancel any previous inner Observable.

![switchMap example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xcccqp1v6cmk0kilgpa.gif)

As you can see in the example, each time the source `A` gets called, the emitting of the previous inner source will get cancelled. The new values will be emitted instead, unless source `A` gets called again.

The cancellation effect is more visible when you call the source `A` multiple times after each other:

![switchMap cancellation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rz2r0n7asbk46lf8t9gf.gif)

### Use case

As stated before, the `switchMap` is most often used together with HTTP GET calls in order to cancel a previous call when a new one is requested soon after. For example, a "refresh" button that will refresh the data in a list. You could spam the button to refresh, but with `switchMap` that would keep canceling all previous clicks.

Another good use case is autocomplete. While typing, the results are being fetched for you. But when the first results come in, it might be that you have already typed some more, which might return a more narrow result. So in that case, it would be handy that the previous call was cancelled to go for the newest one. The best practice is to combine your `switchMap` with a `debounceTime` operator to further improve your autocomplete.

## ConcatMap

`concatMap` will map each source value to an Observable, which is merged into the output Observable in a serialized fashion, waiting for each one to complete before merging the next. So, simply put, they emit values while maintaining order.

![concatMap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpzm7ji2ttq5k558ynpp.gif)

In the example, when the button is clicked for the first time, the inner source starts emitting its values. While it's still emitting, we call the source a second time. We can see that instead of emitting new values for the inner source, it waits until the values of the first time are all emitted. Hence, the order is maintained.

### Use case

`concatMap` is used where order is of importance. A use case could be a character builder in a game where you can choose several options, but some options are dependent on each other. For example, you choose a red hair color and, at the same time, want to add a moustache. The order is important here because you want a red moustache to be added. So the first API call will be with the "red hair" filter, followed by a second API call with the "moustache" filter. Due to the first API call, the second one already knows the hair color is red and can send over a red moustache.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3i247clyv49nkzpn97qz.png"><figcaption>Source: https://www.icy-veins.com/forums/</figcaption>
</figure>

Compare `concatMap` with customers at a pharmacy. They all get served in the order when they arrived. Even if some arrive at the same time, they are handled in a sequential manner.

## ExhaustMap

`exhaustMap` will map each source value to an Observable, which is merged into the output observable only if the previous projected Observable has completed. In simpler terms, the new values are only emitted when the previous ones have all been emitted.
You can remember `exhaustMap` by thinking that you can't make any new calls because it's exhausted from still emitting.

![exhaustMap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dw8smfxd7d4hmnibgygh.gif)

In the example, you see that the source is called multiple times. Only for the first call are the inner source values emitted (the red ones). All subsequent source calls are ignored, except the last call, since all previous inner values have been emitted.

### Use case

A good use case would be to prevent double submissions. If a user keeps spamming the submit button, each call will go through. Using `exhaustMap` will ignore any subsequent calls. You could use `switchMap`, but that would mean that the submission would only go through when the user stopped spamming that submit button.

You could compare it to a restaurant. You give your order to the waiter, and that will make the kitchen start preparing your order. If you call the waiter again with the same order while they're still busy with your current order in the kitchen, it will not make the kitchen start you on a new order. No, they will prepare it, and when it is ready, you can call the waiter again with that same order.

## Airport luggage handling analogy

Let's compare all these operators with the handling of luggage at an airport. At this airport, there is only one conveyor belt to handle luggage. When an airplane arrives, all luggage is put on the conveyor belt so each traveler can retrieve their luggage.

![Airport luggage handling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtaeko110traqfar641p.jpg)

### With `mergeMap`

When an airplane arrives, the luggage is put on the conveyor belt, one after the other. When another airplane arrives as well, the luggage of that one is also put on the conveyor belt, merged with the luggage of the other airplane. So the conveyor belt will show a mix of luggage from the first and second airplane.

### With `switchMap`

When an airplane arrives, the luggage is put on the conveyor belt, one after the other. But as soon as another airplane arrives, they stop putting any luggage from the previous airplane on the conveyor belt. They cancel any further luggage handling for that airplane and begin handling the luggage for the newly arrived airplane.

### With `concatMap`

When an airplane arrives, the luggage is put on the conveyor belt, one after the other. When a new airplane arrives, all the luggage from the first airplane is put on the conveyor belt. Only when all that luggage is handled will the handling of the luggage on the second airplane start.

### With `exhaustMap`

When an airplane arrives, the luggage is put on the conveyor belt, one after the other. When a new airplane arrives and the luggage of the first airplane is still handled, the luggage handling personnel simply ignores the second airplane. The luggage on the second airplane will not be handled.

## TLDR;

- `mergeMap`: come in, merge, come out.
- `switchMap`: come in, merge, come out, but cancel when new ones come in.
- `concatMap`: come in, merge, come out, but maintain order.
- `exhaustMap`: come in, merge, come out, but while ongoing, ignore new ones when they come in.

If you're having trouble choosing the correct higher-order mapping operator, have a look at the following decision tree. It is based on the [operator decision tree](https://rxjs.dev/operator-decision-tree) at RxJS.dev.

![Higher-order mapping operator decision tree](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9jrhzyymukb9hvscko17.png)

Please feel free to play around in the embedded StackBlitz down below. The example images on this blog are based on this StackBlitz.

{% embed https://stackblitz.com/edit/angular-ivy-65wdhd?embed=1&file=src%2Fapp%2Fapp.component.ts&theme=light&view=preview %}

If you have any questions, feel free to contact me!