Skip to the solution
HardFrontend10 min read

What is the difference between switchMap, mergeMap, concatMap, and exhaustMap?

487 views

The problem

Question presented to candidate: "A user can click Save several times while the first request is still in flight, and a search box fires a request on every keystroke. Which RxJS operator would you use for each, and what exactly do switchMap, mergeMap, concatMap and exhaustMap do differently?"

What a strong answer should cover:

  • All four are higher-order mapping (flattening) operators: each outer value is mapped to an inner Observable, and they differ only in what happens when a new outer value arrives while an inner one is still running.
  • switchMap unsubscribes (cancels) the previous inner and switches to the new one: typeahead search, route params.
  • mergeMap runs all inners concurrently and emits in completion order (optional concurrency limit): independent parallel work.
  • concatMap queues: the next inner starts only when the previous completes, so order is preserved (it is mergeMap with concurrency 1).
  • exhaustMap ignores new outer values while an inner is active: login or Save buttons, polling that must not overlap.
  • Cancellation is real: unsubscribing from an HttpClient request aborts it; and error handling belongs inside the inner pipe.

Clarifying questions expected:

  • "If the user clicks again, should the old request be cancelled, queued, run in parallel, or the click ignored?"
  • "Does the order of results matter, and is the server call idempotent?"

Code / implementation expected: Yes β€” a timeline (marble diagram, logged timestamps, or a small component) showing the four behaviours on the same input.

  • rxjs
  • operators

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 9 sections

Read the code

AngularOne Save button, four flattening operators: start, cancel and done logged side by side
import { Component, OnDestroy, signal } from "@angular/core";
import { Observable, OperatorFunction, Subject, Subscription, concatMap, exhaustMap, map, mergeMap, switchMap, timer } from "rxjs";

type Flatten = (project: (id: number) => Observable<string>) => OperatorFunction<number, string>;

// One button feeds the same click stream into four pipelines. Each click
// starts a fake 100 ms "save" request; the log shows which requests start,
// get cancelled, and finish under each flattening operator.
@Component({
  selector: "app-root",
  template: `
    <button (click)="click()">Save (click {{ clicks() + 1 }})</button>
    @for (row of rows; track row.name) {
      <p><b>{{ row.name }}</b>: {{ row.log().join(" | ") }}</p>
    }
  `,
})
export class AppComponent implements OnDestroy {
  private clicks$ = new Subject<number>();
  private subs = new Subscription();
  clicks = signal(0);

  rows = (
    [
      ["switchMap", switchMap],
      ["mergeMap", mergeMap],
      ["concatMap", concatMap],
      ["exhaustMap", exhaustMap],
    ] as [string, Flatten][]
  ).map(([name, op]) => {
    const log = signal<string[]>([]);
    const add = (line: string) => log.update((l) => [...l, line]);
    // A cancellable fake request: logs when it starts, finishes or is cancelled.
    const save = (id: number) =>
      new Observable<string>((sub) => {
        add("start " + id);
        let done = false;
        const inner = timer(100).pipe(map(() => "done " + id)).subscribe((v) => {
          done = true;
          sub.next(v);
          sub.complete();
        });
        return () => {
          inner.unsubscribe();
          if (!done) add("cancel " + id);
        };
      });
    this.subs.add(this.clicks$.pipe(op(save)).subscribe(add));
    return { name, log };
  });

  click() {
    this.clicks.update((n) => n + 1);
    this.clicks$.next(this.clicks());
  }

  ngOnDestroy() {
    this.subs.unsubscribe();
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 101 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What is ViewEncapsulation in Angular?

Continue