Create your own
Lesson illustration

RxJS Flattening Operators: Concurrency Control

Hello! Welcome back to our in-depth study of RxJS.

In our last lesson, we focused on forkJoin, an operator perfect for running a known set of parallel tasks and collecting their results once all have completed. This is ideal for scenarios like fetching initial page data from multiple independent API endpoints.

Today, we address a different, more dynamic challenge: what happens when one stream's emissions trigger new asynchronous operations? This is a cornerstone of reactive programming, turning events into data flows. This lesson covers the learning outcome: Apply and contrast the four primary flattening operators (mergeMap, switchMap, concatMap, exhaustMap) to manage async concurrency patterns.

These four operators are fundamental to building complex, interactive applications. They solve the problem of handling "Observables of Observables" by providing four distinct strategies for managing concurrency. Understanding when to use each is a critical skill for any advanced RxJS developer.

1. The Core Problem: Higher-Order Observables

Imagine you have a stream of user clicks on a button. For each click, you want to make an API call. If you use the map operator we've seen before, you'd end up with a stream that emits Observables, not the API responses themselves. This is called a higher-order Observable or a "stream of streams".

// Source stream of click events
const clicks$ = fromEvent(button, 'click');

// Using map creates a stream of Observables: Observable<Observable<Response>>
const streamOfObservables$ = clicks$.pipe(
  map(() => ajax.getJSON('/api/data')) 
);

To get the actual data, you would have to subscribe to the outer observable, and then inside that subscription, subscribe to each inner observable. This leads to the dreaded "nested subscribe" anti-pattern, which is verbose, hard to manage, and precisely what RxJS aims to help you avoid.

The following article provides an excellent explanation of this problem.

Comprehensive Guide to Higher-Order RxJs Mapping Operators: switchMap, mergeMap, concatMap (and exhaustMap)

The article 'Comprehensive Guide to Higher-Order RxJs Mapping Operators' from Angular University blog clearly explains the concept of higher-order observables and the nested subscription problem that flattening operators solve.

Read the section titled 'What is Higher-Order Observable Mapping'. Focus on understanding why mapping a value to an Observable creates a higher-order Observable and how this leads to the nested subscribe anti-pattern.

The four operators we're studying today—often called flattening operators—solve this elegantly. They all map a source value to an inner Observable, subscribe to it for you, and flatten the emissions into a single output stream. Their key difference lies in the concurrency strategy they employ when the source emits a new value while an inner Observable is still active.

2. The Four Strategies for Flattening

Let's dive into each operator, its strategy, and its most common use case.

concatMap: The Orderly Sequencer

Strategy: concatMap waits for the previous inner Observable to complete before creating and subscribing to the next one. It processes emissions sequentially and guarantees order.

  • Concurrency Model: None. It's serial execution.
  • When to use: When the order of operations is critical and must be preserved. Think of it as a queue.

A perfect use case is ensuring that save operations on a form happen one after another, preventing race conditions where a later save might be overwritten by an earlier, slower one.

Comprehensive Guide to Higher-Order RxJs Mapping Operators: switchMap, mergeMap, concatMap (and exhaustMap)

Let's continue with the Angular University article to see a detailed breakdown of concatMap and its sequential nature.

Read the sections 'Understanding Observable Concatenation' and 'The RxJs concatMap Operator'. Pay close attention to the network log diagram, which clearly shows one request starting only after the previous one finishes.

For a quick video demonstration of this behavior, watch the following clip.

Map, switchMap, mergeMap, flatMap, concatMap, exhaustMap in RxJS - what is the difference?

This clip from Monsterlessons Academy demonstrates concatMap's sequential processing.

Watch from 04:48 to 05:59. Notice how the values from the inner observable appear one by one, with a delay, demonstrating the 'wait for completion' behavior.

mergeMap: The Eager Parallelizer

Strategy: mergeMap (also known by its alias flatMap) subscribes to every inner Observable as soon as it's created. It runs all inner Observables in parallel and merges their emissions into the output stream as they arrive.

  • Concurrency Model: Full parallelism.
  • When to use: When you want to process everything concurrently and don't care about the order of the results. For example, fetching multiple related but independent pieces of data triggered by a single event.

Using mergeMap for the form-saving example would be a bug, as it could trigger multiple parallel PUT requests, leading to potential data corruption.

Comprehensive Guide to Higher-Order RxJs Mapping Operators: switchMap, mergeMap, concatMap (and exhaustMap)

The Angular University article effectively contrasts mergeMap with concatMap.

Read the sections 'Observable Merging' and 'The RxJs mergeMap Operator'. Contrast the network log diagram here with the one for concatMap to see the parallel execution.

And here is a quick video demonstration.

Map, switchMap, mergeMap, flatMap, concatMap, exhaustMap in RxJS - what is the difference?

This clip from Monsterlessons Academy shows mergeMap's parallel execution.

Watch from 02:59 to 04:58. Notice how all values are emitted quickly, as the inner observables are created and run in parallel without waiting for each other.

switchMap: The Impatient Newcomer

Strategy: When the source emits a new value, switchMap unsubscribes from the previous inner Observable and "switches" to the new one. It effectively cancels the old, in-flight operation.

  • Concurrency Model: Only one inner Observable is active at a time—the most recent one.
  • When to use: When you only care about the result from the latest emission. The classic example is a type-ahead search, where you want to cancel previous search requests as the user types a new query.

Comprehensive Guide to Higher-Order RxJs Mapping Operators: switchMap, mergeMap, concatMap (and exhaustMap)

The type-ahead search is the definitive example for switchMap. The Angular University article provides a complete, practical walkthrough.

Read the sections 'Observable Switching' and 'The RxJs switchMap Operator', including the full 'Search TypeAhead' example. The network log showing canceled requests is the key takeaway.

This video provides a concise visual of the "switching" behavior.

Map, switchMap, mergeMap, flatMap, concatMap, exhaustMap in RxJS - what is the difference?

Watch how switchMap only provides the value from the very last observable in this clip from Monsterlessons Academy.

Watch from 05:48 to 06:45. The source emits 0, 1, 2, 3, 4 in quick succession, but only the result from the inner observable for '4' makes it to the output.

exhaustMap: The Busy Worker

Strategy: If an inner Observable is already active, exhaustMap ignores all new source emissions until that inner Observable completes.

  • Concurrency Model: One at a time, but ignores new work while busy.
  • When to use: To prevent multiple concurrent submissions. For example, when a user clicks a "Login" or "Save" button multiple times. You want to process the first click and ignore all subsequent clicks until the first operation is complete.

Comprehensive Guide to Higher-Order RxJs Mapping Operators: switchMap, mergeMap, concatMap (and exhaustMap)

Finally, let's look at the 'ignore while busy' strategy with exhaustMap, again using the excellent Angular University article.

Read 'The Exhaust Strategy' and 'The RxJs exhaustMap Operator'. The example of handling multiple clicks on a save button is a perfect illustration of its purpose.

And here is the final video demonstration.

Map, switchMap, mergeMap, flatMap, concatMap, exhaustMap in RxJS - what is the difference?

This clip from Monsterlessons Academy shows how exhaustMap ignores subsequent emissions.

Watch from 06:35 to 07:21. The source emits 0, 1, 2, 3, 4, but because the first inner observable for '0' is active, all subsequent values are ignored, and only '0' is output.

3. Visualizing the Difference

Marble diagrams are the best way to solidify your understanding of the timing and behavior of these operators. The following diagram from a popular Stack Overflow answer is one of the clearest comparisons available.

To further reinforce these concepts, I highly recommend exploring the interactive examples in the resource this diagram came from.

flatMap, mergeMap, switchMap and concatMap in rxjs?

This Stack Overflow answer provides concise definitions and an excellent interactive code snippet that allows you to run and see the output of each operator.

First, review the bullet-point definitions at the top of the first answer. Then, click the 'Run code snippet' button and click on each operator's name to see its output in the console. Compare the results to the behavior we've discussed.

4. Summary: Choosing the Right Operator

You can typically decide which operator to use by asking a series of questions about your desired concurrency behavior:

QuestionYour Operator is...Concurrency Strategy
Do you need to maintain the order and process one at a time?concatMapSequential (Queue)
Do you only care about the latest value and want to cancel previous in-flight operations?switchMapCancellation (Latest)
Do you want to ignore new requests while one is already running?exhaustMapIgnorance (First)
Do you want to run everything in parallel and handle all results as they come in?mergeMapParallel (All)

Conclusion

In this lesson, we've dissected the four primary flattening operators, which are essential tools for managing asynchronous workflows in RxJS. You've learned how they solve the "nested subscribe" problem and how each one implements a unique concurrency strategy.

Key Takeaways:

  • Higher-Order Observables: A stream that emits other streams. Flattening operators are used to subscribe to these inner streams and merge their values back into a single output stream.
  • concatMap: Guarantees order by processing inner observables sequentially.
  • mergeMap: Processes all inner observables in parallel, with no guarantee of output order.
  • switchMap: Cancels the previous inner observable when a new one is emitted. Ideal for "latest-value-only" scenarios like type-ahead search.
  • exhaustMap: Ignores new source emissions while an inner observable is active. Perfect for preventing duplicate submissions.

In our next lesson, we will introduce Subjects. While flattening operators help manage streams created from other streams, Subjects are special observables that allow you to imperatively push values into a stream from your application code. They act as both an Observable and an Observer, forming a critical bridge between the reactive world and the imperative world and are a key component in building RxJS-based state management solutions.

Can't find a good explanation? Sign up and we'll make it for you

Sign up