These operators help manage multiple tasks that happen over time, like clicking buttons or loading data. They decide how to handle new tasks when old ones are still running.
mergeMap vs concatMap vs exhaustMap in Angular
sourceObservable.pipe(operator(projectFunction))
sourceObservable is the stream of events or data.
operator is one of mergeMap, concatMap, or exhaustMap.
source$.pipe(mergeMap(value => httpCall(value)))
source$.pipe(concatMap(value => httpCall(value)))
source$.pipe(exhaustMap(value => httpCall(value)))
This Angular component listens for button clicks. Each click triggers a fake request that takes 1 second. Using mergeMap, all clicks start requests immediately, so results may overlap. Changing to concatMap makes requests run one after another. Using exhaustMap ignores clicks while a request is running.
import { Component } from '@angular/core'; import { fromEvent, of } from 'rxjs'; import { mergeMap, concatMap, exhaustMap, delay } from 'rxjs/operators'; @Component({ selector: 'app-map-demo', template: ` <button id="btn">Click me</button> <p>{{output}}</p> ` }) export class MapDemoComponent { output = ''; ngOnInit() { const button = document.getElementById('btn'); const clicks$ = fromEvent(button!, 'click'); // Change operator here to mergeMap, concatMap, or exhaustMap clicks$.pipe( mergeMap(() => this.fakeRequest()) ).subscribe(result => { this.output += result + '\n'; }); } fakeRequest() { // Simulates a request that takes 1 second return of('Request done').pipe(delay(1000)); } }
mergeMap is good for parallel tasks but can cause many overlapping results.
concatMap keeps order and waits, useful when sequence matters.
exhaustMap prevents overload by ignoring new tasks during an active one.
mergeMap: runs all tasks at once, results come as they finish.
concatMap: runs tasks one by one, preserving order.
exhaustMap: ignores new tasks if one is running, good for preventing duplicates.
Practice
Solution
Step 1: Understand
mergeMapbehaviormergeMapsubscribes to all inner observables immediately and merges their outputs as they arrive.Step 2: Compare with other operators
concatMapwaits for each inner observable to complete before starting the next,exhaustMapignores new inner observables if one is active, andswitchMapcancels previous inner observables.Final Answer:
mergeMap -> Option DQuick Check:
Runs all tasks at once = mergeMap [OK]
- Confusing concatMap with mergeMap because both handle multiple tasks
- Thinking exhaustMap runs all tasks simultaneously
- Mixing switchMap behavior with mergeMap
concatMap in an Angular RxJS pipe?Solution
Step 1: Recall RxJS pipe usage
Operators likeconcatMapare used insidepipe()method on observables.Step 2: Check syntax correctness
source$.pipe(concatMap(value => inner$)) correctly usessource$.pipe(concatMap(...)). Options A and B misuse method chaining or function call. source$.pipe(map(concatMap(value => inner$))) wrongly nestsconcatMapinsidemap.Final Answer:
source$.pipe(concatMap(value => inner$)) -> Option BQuick Check:
Use pipe with concatMap inside [OK]
- Calling concatMap directly on observable without pipe
- Passing concatMap as argument to map
- Using concatMap as a standalone function
import { of } from 'rxjs';
import { concatMap, delay } from 'rxjs/operators';
of(1, 2, 3).pipe(
concatMap(x => of(x).pipe(delay(100 - x * 10)))
).subscribe(console.log);Solution
Step 1: Understand concatMap behavior with delays
concatMapwaits for each inner observable to complete before starting the next, preserving order.Step 2: Analyze delays and emission order
Even though delays differ (90ms, 80ms, 70ms), concatMap queues them and emits in source order: 1, then 2, then 3.Final Answer:
[1, 2, 3] -> Option CQuick Check:
concatMap preserves order despite delays [OK]
- Assuming output order matches delay times
- Confusing concatMap with mergeMap behavior
- Ignoring that concatMap queues inner observables
exhaustMap. It should ignore new clicks while processing, but it processes all clicks anyway. What is the likely mistake?
buttonClicks$.pipe( exhaustMap(() => fetchData()) ).subscribe();
Solution
Step 1: Check exhaustMap inner function return type
exhaustMapexpects the function to return an observable. IffetchData()returns a promise, it won't behave correctly.Step 2: Understand effect of returning a promise
Promises are not cancellable observables, so exhaustMap can't ignore new clicks properly, causing all clicks to process.Final Answer:
fetchData() returns a promise, not an observable -> Option AQuick Check:
exhaustMap needs observables, not promises [OK]
- Thinking exhaustMap works with promises automatically
- Forgetting to subscribe to the observable
- Confusing exhaustMap with mergeMap usage
searchInput$.pipe( ???(query => performSearch(query)) ).subscribe(results => display(results));
Solution
Step 1: Understand the requirement
The user wants to ignore new search inputs while a search is running, preventing duplicates.Step 2: Match operator behavior
exhaustMapignores new inner observables if one is active, perfect for this use case.mergeMapruns all,concatMapqueues,switchMapcancels previous.Final Answer:
exhaustMap because it ignores new searches while one is running -> Option AQuick Check:
Ignore new inputs during active task = exhaustMap [OK]
- Choosing switchMap which cancels previous instead of ignoring
- Using mergeMap which runs all searches simultaneously
- Using concatMap which queues instead of ignoring
Ready to prove it?
Pass the test and this pattern moves unknown
