Core Differences Between switchMap, mergeMap, and concatMap
These three operators are all used to handle higher-order Observables (Observable of Observable), but they have completely different processing strategies:
1. switchMap
Characteristics: Cancels previous inner Observable, only handles the latest one
How it works:
- When a new value arrives, cancels the previous uncompleted Observable
- Only keeps the result of the latest Observable
- Suitable for scenarios where old requests need to be cancelled
Example code:
javascriptimport { of, interval } from 'rxjs'; import { switchMap, take, delay } from 'rxjs/operators'; // Simulate async request function makeRequest(id) { return of(`Result ${id}`).pipe(delay(1000)); } interval(500).pipe( switchMap(id => makeRequest(id)), take(5) ).subscribe(console.log); // Output: Result 4 // Explanation: Because it triggers every 500ms, but requests take 1000ms // So each time a new request comes, the previous request is cancelled // Eventually only the last request completes
Practical use cases:
- Search box input: Cancel previous search request on each input
- Autocomplete: Only show results for latest input
- Navigation switching: Cancel incomplete page loads
javascript// Search box example searchInput.pipe( switchMap(query => searchAPI(query)) ).subscribe(results => { // Only display results from latest search displayResults(results); });
2. mergeMap
Characteristics: Processes all inner Observables in parallel
How it works:
- Subscribes to all inner Observables simultaneously
- Results from all Observables are emitted
- No order guarantee, results may interleave
Example code:
javascriptimport { of } from 'rxjs'; import { mergeMap, delay } from 'rxjs/operators'; function makeRequest(id) { return of(`Result ${id}`).pipe(delay(id * 200)); } of(1, 2, 3).pipe( mergeMap(id => makeRequest(id)) ).subscribe(console.log); // Output: Result 1, Result 2, Result 3 // Explanation: All requests execute in parallel, output in completion order
Practical use cases:
- Loading multiple resources in parallel
- Batch processing independent tasks
- Concurrent requests that don't require ordering
javascript// Parallel user data loading example merge( getUserProfile(userId), getUserPosts(userId), getUserComments(userId) ).pipe( mergeMap(response => response.json()) ).subscribe(data => { // All data loaded in parallel updateUI(data); });
3. concatMap
Characteristics: Processes inner Observables sequentially, one after another
How it works:
- Subscribes to inner Observables in order
- Subscribes to next only after current Observable completes
- Guarantees result order
Example code:
javascriptimport { of } from 'rxjs'; import { concatMap, delay } from 'rxjs/operators'; function makeRequest(id) { return of(`Result ${id}`).pipe(delay(500)); } of(1, 2, 3).pipe( concatMap(id => makeRequest(id)) ).subscribe(console.log); // Output: Result 1, Result 2, Result 3 // Explanation: Each request executes sequentially, next starts after previous completes
Practical use cases:
- Requests that need to maintain order
- Subsequent requests that depend on previous results
- Preventing server overload
javascript// Sequential file upload example files.pipe( concatMap(file => uploadFile(file)) ).subscribe(result => { // Files uploaded in order console.log('Uploaded:', result); });
Comparison Summary
| Feature | switchMap | mergeMap | concatMap |
|---|---|---|---|
| Execution | Cancels old, keeps latest | Parallel execution | Sequential execution |
| Result order | Only latest result | No order guarantee | Order guaranteed |
| Concurrency | 1 (only 1 at a time) | Unlimited | 1 (only 1 at a time) |
| Use cases | Search, autocomplete | Parallel loading | Sequential processing |
| Performance | Fastest (cancels old) | Fastest (parallel) | Slower (sequential) |
| Memory usage | Low | High | Low |
Selection Guide
Use switchMap when:
- Need to cancel old requests
- Only care about latest results
- Search, autocomplete scenarios
- Avoid unnecessary network requests
Use mergeMap when:
- Need parallel processing
- No dependencies between requests
- Need to maximize performance
- Don't care about result order
Use concatMap when:
- Need to maintain order
- Dependencies between requests
- Need to limit concurrency
- Prevent server overload
Performance Considerations
javascript// Performance comparison example const source = interval(100).pipe(take(10)); // switchMap: Only processes the last one source.pipe( switchMap(x => of(x).pipe(delay(1000))) ).subscribe(); // mergeMap: Processes all in parallel (may cause performance issues) source.pipe( mergeMap(x => of(x).pipe(delay(1000))) ).subscribe(); // concatMap: Processes sequentially (may be slower) source.pipe( concatMap(x => of(x).pipe(delay(1000))) ).subscribe();
Selection in Real Projects
javascript// 1. Search functionality - Use switchMap searchInput.pipe( debounceTime(300), distinctUntilChanged(), switchMap(query => searchAPI(query)) ).subscribe(results => displayResults(results)); // 2. Batch loading - Use mergeMap productIds.pipe( mergeMap(id => getProductDetails(id)) ).subscribe(products => renderProducts(products)); // 3. Sequential operations - Use concatMap commands.pipe( concatMap(command => executeCommand(command)) ).subscribe(result => logResult(result));
Important Notes
- Memory leaks: mergeMap may create many concurrent requests, be mindful of memory usage
- Cancellation logic: switchMap automatically cancels, but concatMap and mergeMap don't
- Error handling: Any inner Observable error will cause the entire stream to fail
- Unsubscription: Always remember to unsubscribe to avoid memory leaks
Best Practices
javascript// 1. Combine with error handling searchInput.pipe( switchMap(query => searchAPI(query).pipe( catchError(error => of([])) ) ) ).subscribe(results => displayResults(results)); // 2. Limit concurrency (using mergeMap) import { mergeMap } from 'rxjs/operators'; source.pipe( mergeMap(value => process(value), 3) // Limit concurrency to 3 ).subscribe(); // 3. Add retry mechanism source.pipe( switchMap(value => apiCall(value).pipe( retry(3), catchError(error => of(defaultValue)) ) ) ).subscribe();