If you are an Angular developer, you have likely encountered the "Nested Subscribe" anti-pattern.
// ❌ The Junior Anti-Pattern
this.route.params.subscribe(params => {
// Nested subscription! Memory leaks and race conditions ahead.
this.userService.getUser(params.id).subscribe(user => {
this.user = user;
});
});
To fix this, we use Higher-Order Mapping Operators. These operators take an Observable, subscribe to it, and "flatten" the result into a single stream.
But RxJS gives us four of them. And choosing the wrong one can cause bugs that are incredibly hard to reproduce (like race conditions or out-of-order saves).
Let's break them down with real-world analogies.
1. mergeMap (The Parallel Map)
The Analogy: A busy highway entrance.
Cars (data) enter the highway whenever they want. They all drive alongside each other. It doesn't matter who finishes first; everyone runs at the same time.
How it works:
- It subscribes to EVERY inner Observable immediately.
- It handles multiple requests in parallel.
- Order is NOT guaranteed (a small request might finish before a large one).
When to use it:
- "Fire and Forget" actions.
- Example: You have a list of items, and the user selects 5 and clicks "Delete". You want all 5 delete requests to run at the same time.
// ✅ Use mergeMap for parallel deletions
idsToDelete$.pipe(
mergeMap(id => this.api.deleteItem(id))
).subscribe();
2. switchMap (The Cancelling Map)
The Analogy: A TV Remote.
You press Channel 5. Before it loads, you press Channel 7. The TV immediately stops trying to load Channel 5 and switches to Channel 7.
How it works:
- Only one inner subscription is active at a time.
- If a new value arrives, it cancels (unsubscribes from) the previous one.
When to use it:
- Read operations / Get requests.
- Example: Search Typeahead. If the user types "Hello", we search. If they immediately type "Hello World", we don't care about the results for "Hello" anymore. Cancel it to save bandwidth.
// ✅ Use switchMap for search or route params
searchTerm$.pipe(
switchMap(term => this.api.search(term))
).subscribe(results => this.results = results);
3. concatMap (The Queue Map)
The Analogy: A Cafeteria Line.
You cannot buy your food until the person in front of you has paid. You must wait your turn.
How it works:
- It queues up requests.
- It waits for the current inner Observable to complete before starting the next one.
- Order is guaranteed.
When to use it:
- Write operations where order matters.
- Example: Auto-Save. If I type "A", then "B", then "C", the API must receive them in that order. If "C" arrives before "B", the database is corrupted.
// ✅ Use concatMap for ordered saves
saveUpdates$.pipe(
concatMap(update => this.api.save(update))
).subscribe();
4. exhaustMap (The Ignoring Map)
The Analogy: A Do Not Disturb sign.
I am working. If you knock on my door, I will ignore you completely until I am finished.
How it works:
- If an inner subscription is running, it ignores any new values from the source.
- Once the inner subscription completes, it is ready to accept new values again.
When to use it:
- Non-idempotent actions (Preventing Spam).
- Example: Login Button. If the user frantically clicks "Login" 10 times, you only want to send ONE request. Ignore the other 9 clicks while the first one is processing.
// ✅ Use exhaustMap for submit buttons
submitClick$.pipe(
exhaustMap(() => this.authService.login(this.form.value))
).subscribe();
Summary Cheat Sheet
| Operator | Behavior | Best For... |
|---|---|---|
| mergeMap | Parallel, no cancelling | Deletes, unrelated data fetching |
| switchMap | Cancels old requests | Search, Filters, Route Parameters |
| concatMap | Waits in line (Queue) | Updates, Saves, Ordered operations |
| exhaustMap | Ignores while busy | Login, Refresh Token, Submit Buttons |
Stop guessing. Choose the operator that fits your data flow.

Top comments (0)