skip to content

In RxJS, what do mergeAll, concatAll, switchAll and exhaustAll do, and how do they relate to the matching *Map operators?

level: middleimportance: nice to knowfreq 26%

answer

  1. a stream of streams
  2. flatten without projecting
  3. map plus All equals Map
  4. concatAll is mergeAll(1)

basics

~20 s

They flatten an observable that already emits observables, using the same strategies as the Map operators: run concurrently, queue, switch to the newest, or ignore while busy. map(project) followed by mergeAll() behaves like mergeMap(project); concatAll() is mergeAll(1).

solid answer

~40 s

A **higher-order observable** emits observables rather than plain values, for example `clicks$.pipe(map(() => request()))`. Subscribing to it gives you observables, not results. The `*All` operators subscribe to those inner observables and emit their values: `mergeAll(concurrent?)` runs them concurrently, `concatAll()` one at a time in order, `switchAll()` only the newest, and `exhaustAll()` ignores new ones while one is active. In RxJS 7.8 they are implemented as their Map counterparts with the identity function — `mergeAll` is `mergeMap(identity, concurrent)`, `switchAll` is `switchMap(identity)` — and `concatAll()` is `mergeAll(1)`. So `map(project)` plus an `*All` equals the matching `*Map(project)`. The old name `exhaust` is deprecated in favour of `exhaustAll`.

code

ts · 11 lines
ts
import { Observable, from, map, mergeAll, concatAll } from 'rxjs';

declare function load(id: number): Observable<string>;

const ids = [1, 2, 3, 4, 5];

// At most two loads in flight
from(ids).pipe(map(id => load(id)), mergeAll(2)).subscribe(console.log);

// Strictly one after another, in id order
from(ids).pipe(map(id => load(id)), concatAll()).subscribe(console.log);

go deeper

for a junior

Recall that the All operators flatten a stream of observables and that switchMap is map followed by switchAll.

for a middle

Explain the identity-based implementations, concatAll as mergeAll(1), and when the observables already exist so the All form fits.

for a senior

Spot nested subscribes and higher-order streams in code reviews and replace them with the flattening strategy the use case needs.

for a principal

Keep a consistent vocabulary for flattening across the team so reviewers can reason about concurrency and ordering at a glance.

## What a higher-order observable is When `map` returns an observable, the result is an **observable of observables**: ```ts const requests$ = ids$.pipe(map(id => load(id))); // Observable<Observable<Item>> ``` Subscribing to `requests$` gives you the inner observables themselves, not the items — and since a cold observable such as an HTTP call is lazy, **no request is made** until something subscribes to each inner. A frequent beginner bug is a nested `subscribe` inside `subscribe` to get at the values; a flattening operator is the proper fix. ## The four flatteners | operator | strategy | RxJS 7.8 implementation | |---|---|---| | `mergeAll(concurrent = Infinity)` | subscribe to every inner, emit values as they come | `mergeMap(identity, concurrent)` | | `concatAll()` | one inner at a time, in arrival order | `mergeAll(1)` | | `switchAll()` | unsubscribe the previous inner when a new one arrives | `switchMap(identity)` | | `exhaustAll()` | ignore new inners while one is active | `exhaustMap(identity)` | Because each is its Map sibling with the identity function, the following pairs are equivalent: - `map(project), mergeAll()` ≡ `mergeMap(project)` - `map(project), concatAll()` ≡ `concatMap(project)` - `map(project), switchAll()` ≡ `switchMap(project)` - `map(project), exhaustAll()` ≡ `exhaustMap(project)` ## When to use the All form The Map form is shorter and more common. The All form fits when **the observables already exist**: 1. A source that naturally emits observables — for example, a stream of `Observable` tasks produced elsewhere. 2. Operators that return higher-order observables, such as `groupBy` or `window`, whose inner streams you want to flatten with a chosen strategy. 3. A static list of observables turned into a stream with `from([...])` and flattened with a concurrency limit: `from(tasks).pipe(mergeAll(2))` runs at most two at a time. ## Naming history worth knowing - `exhaust` was renamed `exhaustAll` in RxJS 7; `exhaust` is deprecated in 7.8 and marked for removal in v8. - `flatMap` is a deprecated alias of `mergeMap`, also marked for removal in v8. - The `resultSelector` overloads of the Map operators are deprecated; use an inner `map` instead. ## Concurrency and ordering, restated - `mergeAll()` with no argument subscribes to every inner immediately; values interleave. - `mergeAll(n)` caps active inners at `n`; the rest wait in an unbounded buffer. - `concatAll()` preserves the order in which inners arrived, not the order in which they would have finished. - Completion: each flattener completes when the outer stream has completed and no inner remains active (or buffered). ## Errors An error from any inner observable errors the flattened output, exactly as with the Map forms. Handling errors per inner, before flattening, keeps the outer stream alive. ## Refactoring a nested subscribe The most common place the All/Map distinction shows up is a review comment on code like this: ```ts ids$.subscribe(id => { load(id).subscribe(item => render(item)); }); ``` The inner subscriptions are never cancelled, run concurrently without a limit, and cannot be composed further. The flattened version states the strategy explicitly: ```ts ids$.pipe(switchMap(id => load(id))).subscribe(item => render(item)); ``` or, equivalently, `map(id => load(id))` followed by `switchAll()`. Choosing `mergeAll`, `concatAll` or `exhaustAll` instead is the same decision as choosing among the Map operators. ## Interview angle The question usually tests whether a candidate understands that `switchMap` is "map, then switch": naming the two steps, recognising a nested subscribe as a missing flattening operator, and knowing that `concatAll()` is merge with one slot is a strong signal.

  • Why does ids$.pipe(map(id => load(id))).subscribe(...) make no requests?
    The subscriber receives the inner observables themselves. A cold inner such as an HTTP call is lazy, so nothing runs until it is subscribed; a flattening operator such as `mergeAll` or a Map operator performs that subscription.
  • What replaced the exhaust operator in RxJS 7?
    `exhaustAll`. The old `exhaust` name still exists in 7.8 but is deprecated and marked for removal in v8; it has the same behaviour, ignoring new inner observables while one is active.

saying these in an interview costs you the question

  • map with an observable-returning function subscribes to the inner observables.
  • concatAll runs inner observables in parallel and sorts the results.
  • The All operators take a projection function like the Map operators.
  • A nested subscribe is the recommended way to reach inner values.
  • exhaust is the current name for exhaustAll.