Bird
Raised Fist0
Angularframework~10 mins

mergeMap vs concatMap vs exhaustMap in Angular - Visual Side-by-Side Comparison

Choose your learning style10 modes available
Concept Flow - mergeMap vs concatMap vs exhaustMap
Source Observable emits value
↓
Operator handles inner Observable
↓
Emit values from inner Observables
↓
Output Observable emits merged results
When the source emits, each operator decides how to handle inner Observables: mergeMap runs all concurrently, concatMap queues them, exhaustMap ignores new while busy.
Execution Sample
Angular
source$.pipe(
  mergeMap(val => innerObservable(val))
).subscribe(console.log);
This code runs all inner Observables at once, emitting their results as they come.
Execution Table
StepSource EmitOperatorInner Observable ActionOutput Emitted
1AmergeMapStart innerObservable(A)Value A1
2BmergeMapStart innerObservable(B) concurrentlyValue B1
3A inner emits againmergeMapEmit innerObservable(A) valueValue A2
4B inner emits againmergeMapEmit innerObservable(B) valueValue B2
5CconcatMapQueue innerObservable(C)No output yet
6A inner completesconcatMapStart innerObservable(C)Value C1
7DconcatMapQueue innerObservable(D)Value C2
8C inner completesconcatMapStart innerObservable(D)Value D1
9EexhaustMapIgnore new since inner activeNo output
10D inner completesexhaustMapNo action: ignored emissions (like E) are droppedNo output
11FexhaustMapStart innerObservable(F)Value F1
12F inner emits againexhaustMapEmit innerObservable(F) valueValue F2
13No more source emits--Complete output
14End--All inner Observables completed
💡 Execution stops when source completes and all inner Observables finish emitting.
Variable Tracker
VariableStartAfter Step 2After Step 6After Step 10Final
Active inner Observables (mergeMap)02 (A,B)1 (B,C)00
Queued inner Observables (concatMap)001 (D)00
Active inner Observable (exhaustMap)00000
Key Moments - 3 Insights
Why does concatMap wait before starting the next inner Observable?
concatMap queues new inner Observables until the current one completes, as shown in execution_table rows 5-8 where C waits until A completes.
Why does exhaustMap ignore new source emissions while an inner Observable is active?
exhaustMap ignores (drops) new source values if an inner Observable is running, shown in row 9 where E is ignored while D active. Ignored emissions are not processed later.
How does mergeMap handle multiple inner Observables?
mergeMap runs all inner Observables concurrently, emitting their values as they arrive, as seen in rows 1-4.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table at step 5, what does concatMap do when source emits 'C'?
AStarts innerObservable(C) immediately
BQueues innerObservable(C) to start later
CIgnores the emission 'C'
DCancels previous innerObservable
💡 Hint
Check execution_table row 5 where concatMap queues innerObservable(C) without output.
At which step does exhaustMap start processing innerObservable(F)?
AStep 9
BStep 10
CStep 11
DStep 12
💡 Hint
Look at execution_table row 11 where exhaustMap starts innerObservable(F) after previous completes.
According to variable_tracker, how many active inner Observables does mergeMap have after step 2?
A2
B0
C1
D3
💡 Hint
See variable_tracker row for mergeMap active inner Observables after step 2.
Concept Snapshot
mergeMap: runs all inner Observables at once, outputs as they come.
concatMap: queues inner Observables, runs one at a time in order.
exhaustMap: ignores new source values while inner Observable is active.
Use mergeMap for parallel, concatMap for ordered, exhaustMap to ignore overlaps.
Full Transcript
This visual execution compares three Angular RxJS operators: mergeMap, concatMap, and exhaustMap. When the source Observable emits a value, each operator decides how to handle the inner Observable created from that value. mergeMap runs all inner Observables concurrently, emitting their results as they arrive. concatMap queues inner Observables and runs them one after another, waiting for each to complete before starting the next. exhaustMap ignores new source emissions if an inner Observable is still active, only starting a new one after the current completes. The execution table shows step-by-step how each operator behaves with source emissions and inner Observable outputs. The variable tracker highlights how many inner Observables are active or queued at key steps. Key moments clarify common confusions about queuing, ignoring, and concurrency. The visual quiz tests understanding by referencing specific steps and variable states. The snapshot summarizes when to use each operator based on concurrency and ordering needs.

Practice

(1/5)
1. Which RxJS operator runs all inner tasks at the same time and emits results as they complete, without waiting for order?
easy
A. switchMap
B. concatMap
C. exhaustMap
D. mergeMap

Solution

  1. Step 1: Understand mergeMap behavior

    mergeMap subscribes to all inner observables immediately and merges their outputs as they arrive.
  2. Step 2: Compare with other operators

    concatMap waits for each inner observable to complete before starting the next, exhaustMap ignores new inner observables if one is active, and switchMap cancels previous inner observables.
  3. Final Answer:

    mergeMap -> Option D
  4. Quick Check:

    Runs all tasks at once = mergeMap [OK]
Hint: All tasks at once means mergeMap [OK]
Common Mistakes:
  • Confusing concatMap with mergeMap because both handle multiple tasks
  • Thinking exhaustMap runs all tasks simultaneously
  • Mixing switchMap behavior with mergeMap
2. Which of the following is the correct syntax to use concatMap in an Angular RxJS pipe?
easy
A. concatMap(source$, value => inner$)
B. source$.pipe(concatMap(value => inner$))
C. source$.concatMap(value => inner$)
D. source$.pipe(map(concatMap(value => inner$)))

Solution

  1. Step 1: Recall RxJS pipe usage

    Operators like concatMap are used inside pipe() method on observables.
  2. Step 2: Check syntax correctness

    source$.pipe(concatMap(value => inner$)) correctly uses source$.pipe(concatMap(...)). Options A and B misuse method chaining or function call. source$.pipe(map(concatMap(value => inner$))) wrongly nests concatMap inside map.
  3. Final Answer:

    source$.pipe(concatMap(value => inner$)) -> Option B
  4. Quick Check:

    Use pipe with concatMap inside [OK]
Hint: Always use pipe() with concatMap inside [OK]
Common Mistakes:
  • Calling concatMap directly on observable without pipe
  • Passing concatMap as argument to map
  • Using concatMap as a standalone function
3. Given the code below, what will be the output order of emitted values?
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);
medium
A. [3, 2, 1]
B. [1, 3, 2]
C. [1, 2, 3]
D. [2, 1, 3]

Solution

  1. Step 1: Understand concatMap behavior with delays

    concatMap waits for each inner observable to complete before starting the next, preserving order.
  2. 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.
  3. Final Answer:

    [1, 2, 3] -> Option C
  4. Quick Check:

    concatMap preserves order despite delays [OK]
Hint: concatMap keeps order even if inner delays differ [OK]
Common Mistakes:
  • Assuming output order matches delay times
  • Confusing concatMap with mergeMap behavior
  • Ignoring that concatMap queues inner observables
4. You have this code snippet using exhaustMap. It should ignore new clicks while processing, but it processes all clicks anyway. What is the likely mistake?
buttonClicks$.pipe(
  exhaustMap(() => fetchData())
).subscribe();
medium
A. fetchData() returns a promise, not an observable
B. Using exhaustMap instead of mergeMap
C. Not subscribing to the observable
D. Not returning an observable inside exhaustMap

Solution

  1. Step 1: Check exhaustMap inner function return type

    exhaustMap expects the function to return an observable. If fetchData() returns a promise, it won't behave correctly.
  2. 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.
  3. Final Answer:

    fetchData() returns a promise, not an observable -> Option A
  4. Quick Check:

    exhaustMap needs observables, not promises [OK]
Hint: exhaustMap requires observables, not promises [OK]
Common Mistakes:
  • Thinking exhaustMap works with promises automatically
  • Forgetting to subscribe to the observable
  • Confusing exhaustMap with mergeMap usage
5. You want to handle user search input so that if a search is running, new inputs are ignored until it finishes. Which operator should you use and why?
searchInput$.pipe(
  ???(query => performSearch(query))
).subscribe(results => display(results));
hard
A. exhaustMap because it ignores new searches while one is running
B. concatMap because it queues searches and runs them one by one
C. mergeMap because it runs all searches simultaneously
D. switchMap because it cancels previous searches on new input

Solution

  1. Step 1: Understand the requirement

    The user wants to ignore new search inputs while a search is running, preventing duplicates.
  2. Step 2: Match operator behavior

    exhaustMap ignores new inner observables if one is active, perfect for this use case. mergeMap runs all, concatMap queues, switchMap cancels previous.
  3. Final Answer:

    exhaustMap because it ignores new searches while one is running -> Option A
  4. Quick Check:

    Ignore new inputs during active task = exhaustMap [OK]
Hint: Ignore new tasks while running = exhaustMap [OK]
Common Mistakes:
  • Choosing switchMap which cancels previous instead of ignoring
  • Using mergeMap which runs all searches simultaneously
  • Using concatMap which queues instead of ignoring
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard