In RxJS, what is the difference between merge() and concat() when you combine two observables into one stream?
answer
- all at once, or one after another
- interleaved versus source order
- concat waits for completion
- merge's trailing concurrent number
basics
~10 smerge subscribes to every source at once and forwards values as they arrive, interleaved; concat subscribes to the next source only after the previous one completes, so values come out source by source.
solid answer
~40 s`merge(a$, b$)` subscribes to both immediately and emits values in whatever order they arrive; it completes when **all** sources have completed and errors as soon as any source errors. `concat(a$, b$)` subscribes to `a$` first, forwards everything it emits, and only when `a$` **completes** subscribes to `b$`. The output is therefore ordered source by source — but if `a$` never completes, `b$` is never subscribed at all. The RxJS docs describe `concat` as `merge` with its optional trailing `concurrent` argument set to `1`. Inside a `pipe`, the current forms are `mergeWith` and `concatWith`; the pipeable `merge` and `concat` operators are deprecated in RxJS 7.
code
ts · 9 linesimport { concat, of, Observable } from 'rxjs';
interface Report { rows: number }
declare const cachedReport: Report;
declare const fetchReport$: Observable<Report>; // emits once, then completes
// Show the cached report immediately, then replace it with the fresh one.
const report$ = concat(of(cachedReport), fetchReport$);go deeper
Recall the one-line difference: merge runs all sources together and interleaves; concat runs them one after another, each waiting for the previous to complete.
Explain completion and error rules for both, the concurrent argument, why concat equals merge with concurrency one, and the mergeWith and concatWith pipeable forms.
Spot the traps in real pipelines: a never-completing first source stalling concat, a hot source losing values while waiting, and unbounded merge concurrency hammering a backend.
Frame the choice as an ordering-versus-latency contract, and decide where a pipeline should bound concurrency rather than leaving it to each call site.
## Two static functions, two subscription policies `merge` and `concat` are **static creation functions** exported from `'rxjs'`: each takes several observables and returns one observable that re-emits their values. Neither transforms values or pairs them together — every value from every source comes out unchanged, one at a time. What differs is **when each source is subscribed**, and that decides the output order. - **`merge`** subscribes to all sources immediately. Output order is arrival order. - **`concat`** subscribes to one source at a time, moving to the next only when the current one completes. Output order is source order. ## merge in detail - Values are forwarded the instant they arrive, so two sources' values **interleave** by time. - The result completes only when **every** source has completed. - If any source errors, the error is forwarded and the remaining sources are unsubscribed. - A trailing number caps how many sources run at once: `merge(a$, b$, c$, 2)` subscribes to `a$` and `b$`, and subscribes to `c$` only when one of them completes. Without it the limit is unbounded. Typical uses: combining several event streams that mean the same thing (a refresh button, a keyboard shortcut and a periodic timer that all trigger a reload), or running a handful of independent side effects at once. ## concat in detail 1. Subscribe to the first source and forward its values. 2. When it completes, subscribe to the second source. 3. Repeat until the last source completes; then the result completes. 4. If any source errors, the result errors and the sources after it are never subscribed. Because later sources are subscribed late, `concat` has two classic traps: - **A never-completing source blocks everything after it.** `concat(interval(1000), b$)` never reaches `b$`. - **A hot source subscribed late misses what it already emitted.** If `b$` is a `Subject` that emitted while `a$` was still running, those values are gone by the time `concat` subscribes to it. Typical uses: emit a cached or placeholder value and then the fresh one (`concat(of(cached), fresh$)`), or run a fixed sequence of steps strictly one after another. ## Side by side | | `merge(a$, b$)` | `concat(a$, b$)` | |---|---|---| | Subscribes to `b$` | immediately | after `a$` completes | | Output order | by arrival time | all of `a$`, then all of `b$` | | Completes when | every source completes | the last source completes | | `a$` never completes | `b$` still flows | `b$` is never subscribed | | Concurrency | unbounded, or the trailing number | always one | | Pipeable form (RxJS 7) | `a$.pipe(mergeWith(b$))` | `a$.pipe(concatWith(b$))` | The docs make the relationship explicit: `concat` is equivalent to `merge` with `concurrent` set to `1` — with one source allowed at a time, the next one can only start when the current one finishes. ```ts import { concat, merge, map, take, timer } from 'rxjs'; const slow$ = timer(0, 300).pipe(map((i) => `slow${i}`), take(2)); const fast$ = timer(0, 100).pipe(map((i) => `fast${i}`), take(3)); merge(slow$, fast$).subscribe((v) => console.log('merge', v)); // slow0 fast0 fast1 fast2 slow1 (interleaved by time) concat(slow$, fast$).subscribe((v) => console.log('concat', v)); // slow0 slow1 fast0 fast1 fast2 (fast$ starts only after slow$ completes) ``` ## Static function or operator Inside a `pipe`, use the `With` operators: `a$.pipe(mergeWith(b$))` behaves like `merge(a$, b$)`, and `a$.pipe(concatWith(b$))` like `concat(a$, b$)`. The older pipeable `merge` and `concat` operators still exist in `'rxjs/operators'` but are deprecated in RxJS 7 and slated for removal in v8, because their names clash with the static functions. ## Not to be confused with `mergeMap` and `concatMap` also use the words merge and concat, but they map **each value** of one stream to a new inner observable and flatten the results. `merge` and `concat` join a **fixed list** of sources you already have.
- What does the trailing number in merge(a$, b$, c$, 2) do?It caps concurrency: `merge` subscribes to at most two sources at a time. Here `a$` and `b$` start immediately and `c$` is subscribed only when one of them completes. Without the number the limit is unbounded. With the number set to `1` the behaviour is exactly `concat`.
- Why can concat(a$, b$) lose values from b$ even when a$ does complete?`concat` subscribes to `b$` only after `a$` completes. If `b$` is hot — a `Subject` or an event stream that emits whether or not anyone listens — any value it emitted while `a$` was still running had no subscriber and is lost. A cold `b$` is unaffected, since its work starts on subscription.
saying these in an interview costs you the question
- merge emits all of the first source's values before the second's
- concat subscribes to every source up front and buffers the later ones
- concat moves to the next source when the current one emits its first value
- merge completes as soon as any one of its sources completes
- concat(a$, b$) still reaches b$ even if a$ never completes