Skip to content

switchMap vs mergeMap vs concatMap vs exhaustMap#

Let's break down the differences between the four higher-order mapping strategies: switchMap, mergeMap, concatMap, and exhaustMap.

All of them map each value from a source (outer) Observable to a new (inner) Observable. The key difference lies in how they handle the subscription and emissions of these inner Observables, especially when the source Observable emits values rapidly.

Here’s a theoretical comparison:

  1. switchMap

    • Strategy: Cancellation / Focus on Latest.
    • Behavior: When the source Observable emits a value, switchMap maps it to an inner Observable and subscribes. If the source emits a new value before the current inner Observable completes, switchMap will unsubscribe from the previous inner Observable (cancelling its ongoing work and discarding any potential future emissions from it) and then subscribe to the new inner Observable created from the latest source value.
    • Concurrency: Only one inner Observable (the latest one) is active at any given time.
    • Order: Output values come only from the most recent inner Observable. The order depends on that inner Observable, but older inner streams are cancelled entirely.
    • Use When: You only care about the results corresponding to the most recent source emission. Useful for scenarios like type-ahead search suggestions where previous requests become irrelevant.
  2. mergeMap (alias: flatMap)

    • Strategy: Concurrency / Merging.
    • Behavior: When the source Observable emits a value, mergeMap maps it to an inner Observable and subscribes. If the source emits a new value, mergeMap does not cancel any previous inner Observables. It simply creates and subscribes to the new inner Observable, allowing multiple inner Observables to run concurrently.
    • Concurrency: Can have multiple inner Observables running in parallel. The level of concurrency can optionally be limited by passing a second argument to mergeMap.
    • Order: Output values from all active inner Observables are merged into a single stream as they arrive. The order of output values is not guaranteed to match the order of source emissions; it depends on how quickly each inner Observable emits.
    • Use When: You want to handle all source emissions by triggering potentially long-running operations and need them to run in parallel for efficiency. The order of completion doesn't matter as much as getting all the results eventually. Useful for making multiple concurrent API calls.
  3. concatMap

    • Strategy: Sequential / Queueing.
    • Behavior: When the source Observable emits a value, concatMap maps it to an inner Observable. It subscribes to this inner Observable. If the source emits a new value before the current inner Observable completes, concatMap will wait. It holds onto the new source value and only maps/subscribes to its corresponding inner Observable after the current one has finished completely.
    • Concurrency: Only one inner Observable is active at any given time. Others are effectively queued.
    • Order: Output values are guaranteed to be in the same order as the source emissions because each inner Observable is processed sequentially.
    • Use When: The order of execution is critical. You need to ensure that the operation triggered by one source value completes fully before starting the operation for the next source value. Useful for sequential API updates or processing items in a strict order.
  4. exhaustMap

    • Strategy: Ignore While Busy.
    • Behavior: When the source emits a value, exhaustMap maps it to an inner Observable and subscribes, exactly once. While that inner Observable is still active, any new source values are dropped entirely: not queued, not cancelled into a new request, simply discarded. Once the inner Observable completes, the next source value to arrive is processed.
    • Concurrency: One inner Observable at a time; excess source values are lost.
    • Order: Output comes only from inner Observables that were actually started; dropped values produce nothing.
    • Use When: The first trigger should win and repeats are noise: submit buttons, login attempts, manual refresh. Protects the in-flight operation instead of restarting or queueing.

In a Nutshell:

Operator Inner Observable Handling Concurrency Order Analogy
switchMap Cancels previous, switches to latest Only latest Latest matters Restless TV channel surfing
mergeMap Runs all concurrently High (Parallel) Interleaved Opening many browser tabs
concatMap Waits for completion, processes sequentially One at a time Strict Waiting in a single queue
exhaustMap Ignores new values while busy One at a time First wins Busy phone line

The Classic Interview Scenario: Triple-Clicking Save#

A user clicks "Save" three times in one second. What happens?

Operator Requests fired Result
mergeMap 3, in parallel Three saves race each other; final state unpredictable
concatMap 3, one after another Three sequential saves; slow, but ordered
switchMap 3 started, first 2 cancelled Only the last response arrives; earlier writes may still land
exhaustMap 1 Clicks 2 and 3 ignored; the in-flight save finishes untouched

For a save button, exhaustMap is almost always the intended behavior, paired with a disabled state for feedback.

Quick Decision Guide#

  • Only the latest result matters (search, route params, refresh): switchMap
  • All results matter and can run in parallel (independent writes, fan-out reads): mergeMap
  • All results matter and order matters (queues, ordered writes): concatMap
  • Only the first trigger matters until it finishes (submit, login): exhaustMap

One closing fact that ties the family together: concatMap(project) is just mergeMap(project, 1), and all four share the same signature, so swapping strategies is a one-word change.

Interview Q&A#

Walk me through choosing between the four for a typeahead search.

The requirement is "only the latest term's results matter". A new keystroke makes the in-flight request worthless, so it should be cancelled: that is switchMap. mergeMap would let stale responses overwrite fresh ones, concatMap would queue every keystroke's request, and exhaustMap would ignore new terms while an old request runs, which is exactly backwards for search.

When is switchMap the wrong choice for HTTP?

For writes. Cancelling a POST request does not cancel the server-side work; the mutation may still land while your client has thrown away the response. For saves you want exhaustMap (ignore repeat triggers) or concatMap (queue them in order), so every response you act on corresponds to a request you know about.

What does mergeMap's concurrency argument do?

mergeMap(project, n) caps the number of inner Observables running at once; further source values wait in a queue. It is the middle ground between full parallelism (mergeMap default, infinite) and strict sequence (concatMap, which is literally mergeMap(project, 1)). A typical use is limiting fan-out, for example uploading files three at a time.

A save button fires duplicate requests in production. Diagnose it.

Almost always a flattening-strategy bug: clicks mapped with mergeMap (every click fires) or switchMap (requests cancelled client-side but mutations still land server-side). The fix is exhaustMap, which ignores clicks while a save is in flight, plus disabling the button for user feedback. If duplicates persist, look for multiple subscriptions to the same click stream.