Deep Dive: Solving Race Conditions and Concurrency with RxJS Operators

Leader posted 3 min read

Race conditions are the nightmares of frontend development. They happen when your code assumes that network request A will finish before network request B, but the internet decides otherwise.

In React or Vue (without RxJS), managing complex concurrency often requires boolean flags (isLoading), timestamp checks, or AbortControllers.

In Angular, we solve this elegantly with RxJS Mapping Operators.

But RxJS gives us four different tools: mergeMap, switchMap, concatMap, and exhaustMap. Choosing the wrong one isn't just a style choice; it determines whether your app creates bugs or prevents them.

Let's break down exactly which concurrency strategy each operator uses.

1. switchMap: The "Latest and Greatest" (Fixing Read Races)

The Strategy: Cancel the previous request if a new one arrives.

The Scenario:
Imagine a user is on a "User Details" page.

  1. They click "User A". (Request A starts - Slow Network).
  2. They get bored and click "User B" immediately. (Request B starts - Fast Network).

The Bug (without switchMap):
Request B finishes first and shows User B. Then, Request A finishes and overwrites the screen with User A. The URL says "User B", but the screen shows "User A". This is a dirty UI state.

The Fix:

this.route.params.pipe(
  // When params change, cancel any pending request!
  switchMap(params => this.userService.getUser(params.id))
).subscribe(user => {
  // We are GUARANTEED that 'user' matches the current URL.
  this.displayUser(user);
});

Best For: GET requests (Search, Route Params, Filters).


2. concatMap: The "Single File Line" (Fixing Write Races)

The Strategy: Wait for the current request to finish before starting the next.

The Scenario:
An "Auto-Save" feature in a text editor.

  1. User types "Hello". (Request A sent).
  2. User immediately types " World". (Request B sent).

The Bug (with switchMap):
If you used switchMap, Request B would cancel Request A. The server only receives " World". The word "Hello" is lost forever.

The Fix:
We need a Queue. Request B must wait in line.

this.textInput.valueChanges.pipe(
  // Queue the requests. Request B waits for Request A to complete.
  concatMap(text => this.api.saveDraft(text))
).subscribe();

Best For: PUT/POST requests where order matters (Auto-saves, sequential steps).


3. mergeMap: The "Free-for-All" (Parallel Execution)

The Strategy: Run everything immediately. I don't care about order.

The Scenario:
A "Bulk Delete" feature. The user selects 5 items from a list and clicks "Delete Selected".

The Strategy:
We don't want to wait for Item 1 to delete before starting Item 2 (too slow). And we certainly don't want to cancel Item 1 if Item 2 starts (bug). We want them all to fly to the server at once.

The Fix:

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

from(idsToDelete).pipe(
  // Fire 5 network requests in parallel
  mergeMap(id => this.api.deleteItem(id))
).subscribe(deletedId => {
  console.log(`Deleted ${deletedId}`);
});

Best For: DELETE requests or fetching unrelated data (e.g., getting Widget A and Widget B at the same time).


4. exhaustMap: The "Do Not Disturb" (Spam Prevention)

The Strategy: Ignore new requests while I am busy.

The Scenario:
A "Pay Now" button or a Login form.
A nervous user clicks the button 10 times in one second because the spinner didn't appear instantly.

The Bug (with mergeMap):
You charge the user's credit card 10 times.

The Bug (with switchMap):
You cancel the payment 9 times, and only the 10th one works (maybe).

The Fix:
We want to completely ignore clicks 2 through 10.

fromEvent(this.payButton, 'click').pipe(
  // If a payment is processing, IGNORE this click.
  exhaustMap(() => this.paymentService.chargeCard())
).subscribe();

Best For: Non-idempotent POST requests (Login, Payment, Submit).


Summary Cheat Sheet

Operator Concurrency Strategy Ideal Use Case
switchMap Kill the previous Reads (Search, Routing)
concatMap Wait in line Ordered Writes (Save)
mergeMap Run in parallel Parallel Writes (Delete)
exhaustMap Ignore the new Submit Buttons (Auth)

RxJS operators are tools for managing time. Master these four, and you master concurrency.

1 Comment

1 vote

More Posts

The Complete Guide to Angular Subscriptions: Template vs. Component Logic

Habib - Nov 19

Animate Like a Pro: Build a Modern Scroll-Triggered Website with GSAP

iCreator Studio - Jun 30

JavaScript: Array Iteration and DOM Manipulation in Game Development

Michael Larocca - Oct 13

Beyond the Array: Why Normalized Maps and Sets Supercharge Your Frontend Performance

Waffeu Rayn - Oct 6

Blockchain and Frontend Development: Building the Decentralized Web

Toni Naumoski - Aug 13
chevron_left