JavaScript

RxJS Best Practices

RxJS is powerful specifically because it composes complex async event handling declaratively — and that same power makes subscription management and operator misuse the most common source of real RxJS bugs.

Last updated 2026-09-08

1

Always unsubscribe, or use operators that complete automatically

A subscription that's never cleaned up keeps its callback running (and holding references) indefinitely, a classic memory-leak pattern. Use takeUntil, first(), or framework-level tools (like Angular's async pipe) that handle cleanup automatically where possible.

2

Avoid nested subscriptions — use flattening operators instead

Subscribing inside another subscription's callback (to chain a dependent async operation) is a common anti-pattern with hard-to-manage cleanup. switchMap, mergeMap, or concatMap express the same dependent-chain logic in a single, more manageable stream.

3

Choose the right flattening operator deliberately

switchMap cancels the previous inner observable when a new value arrives (right for search-as-you-type); mergeMap runs all inner observables concurrently; concatMap queues them sequentially. Picking the wrong one produces subtle race-condition bugs.

4

Use the async pipe (in Angular) instead of manual subscribe in templates

The async pipe subscribes and unsubscribes automatically tied to the component's lifecycle, eliminating a whole category of manual subscription-management bugs.

5

Keep side effects in tap(), not scattered inside map() or subscribe()

map() should be a pure transformation; putting side effects (logging, triggering another action) inside it muddles the stream's actual data-transformation logic. tap() exists specifically to make side effects explicit and separate.

Common Mistakes to Avoid

  • Subscribing without ever unsubscribing, causing a memory leak
  • Nested subscribe() calls instead of using switchMap/mergeMap/concatMap to flatten dependent streams
  • Using mergeMap when switchMap's cancel-previous behavior was actually needed (e.g. search-as-you-type), causing race conditions
  • Side effects hidden inside map() instead of using tap()
  • Manual subscribe() in an Angular template instead of the async pipe

Frequently Asked Questions

When should I use switchMap vs mergeMap?

switchMap when a new value should cancel any in-flight previous request (typeahead search is the classic example); mergeMap when every request should run to completion independently and concurrently (like triggering several unrelated uploads).

Is RxJS overkill for simple state management?

Often, yes — for straightforward local component state, a simpler primitive (plain variables, or signals in newer Angular) is usually more appropriate. RxJS earns its complexity for genuinely complex async event composition, not as a default for all state.