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
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.
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.
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.
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.
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.