RxJS
RxJS Operators Guide with Examples
A practical RxJS guide focused on the most important operators, with beginner-friendly explanations, real use cases, Angular patterns, examples, traps, and practice questions.
Operators
Observables
Angular
Examples
Practice Questions
Published: Dec 2019 Updated: May 2026
Quick Summary
- RxJS is: a library for working with values over time using Observables.
- Operator means: a function that takes an Observable and returns a new Observable.
- Use RxJS when: values arrive over time, can be cancelled, combined, filtered, retried, shared, or transformed.
- Common examples: search input, button clicks, route changes, HTTP calls, WebSockets, timers, forms, and app state.
- Biggest skill: choosing the right operator for the user experience.
Mental model: source Observable -> operators inside
pipe() -> subscriber or async pipe.Operator Categories
| Category | Important operators | Purpose |
|---|---|---|
| Creation | of, from, fromEvent, interval, timer, defer, throwError | Create Observable sources. |
| Transformation | map, pluck, scan, reduce, bufferTime, pairwise | Change emitted values. |
| Filtering | filter, take, first, last, skip, distinctUntilChanged, debounceTime, throttleTime | Control which values pass through. |
| Combination | combineLatest, withLatestFrom, forkJoin, zip, merge, concat, race | Work with multiple streams. |
| Flattening | switchMap, mergeMap, concatMap, exhaustMap | Map a value to another Observable and flatten the result. |
| Error and retry | catchError, retry, retryWhen, finalize | Recover, retry, and clean up. |
| Multicasting | share, shareReplay, publish | Share subscriptions and cached values. |
Creation Operators
| Operator | Explanation | Example use |
|---|---|---|
of | Creates an Observable from fixed values. | Return fallback data such as of([]). |
from | Creates an Observable from array, promise, iterable, or similar source. | Convert a Promise or array into a stream. |
fromEvent | Creates a stream from DOM events. | Listen to clicks, input, scroll, or keyup. |
interval | Emits numbers repeatedly after a fixed interval. | Polling, timer ticks, live counters. |
timer | Emits after a delay, optionally repeatedly. | Delayed action or scheduled polling. |
defer | Creates a fresh Observable factory for each subscription. | Run current-time logic only when subscribed. |
throwError | Creates an Observable that errors. | Return an error from inside an operator. |
import { of, from, fromEvent, interval, timer, defer } from 'rxjs';
of('Angular', 'React', 'Vue');
from(fetch('/api/users'));
fromEvent(document, 'click');
interval(1000);
timer(2000);
defer(() => of(new Date().toISOString()));
Transformation Operators
| Operator | Explanation | Example use |
|---|---|---|
map | Transforms each emitted value. | Convert API response into view model. |
scan | Accumulates state over time and emits each step. | Counter, reducer-style state, running totals. |
reduce | Accumulates all values and emits once on completion. | Final sum after finite stream completes. |
pairwise | Emits previous and current value together. | Compare route, scroll, or form changes. |
bufferTime | Collects values for a time window. | Batch frequent events. |
toArray | Collects all values into an array when complete. | Collect results from finite stream. |
users$ = this.http.get<UserDto[]>('/api/users').pipe(
map(users => users.map(user => ({
id: user.id,
label: `${user.firstName} ${user.lastName}`,
active: user.status === 'ACTIVE'
})))
);
Filtering and Rate-Control Operators
| Operator | Explanation | Use when |
|---|---|---|
filter | Passes values matching a condition. | Ignore invalid form values. |
take | Takes a fixed number of values, then completes. | Read first 1 or first N values. |
takeUntil | Completes when another Observable emits. | Cleanup on destroy or cancellation signal. |
first | Emits the first matching value. | Need first valid event. |
skip | Ignores the first N values. | Ignore initial default state. |
distinctUntilChanged | Skips same consecutive value. | Do not repeat search for same term. |
debounceTime | Waits for quiet time before emitting. | Search box after typing stops. |
throttleTime | Emits at most once per time window. | Button click or scroll rate limiting. |
auditTime | Emits latest value after each time window. | UI updates during scroll/resize. |
sampleTime | Samples latest value at intervals. | Periodic snapshot of frequent events. |
searchResults$ = fromEvent<InputEvent>(searchInput, 'input').pipe(
map(event => (event.target as HTMLInputElement).value.trim()),
filter(term => term.length >= 2),
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.api.search(term))
);
Combination Operators
| Operator | Explanation | Best use |
|---|---|---|
combineLatest | Combines latest values from multiple streams after each has emitted. | View model from filters, sort, page, and data. |
withLatestFrom | Main stream emits while sampling latest values from others. | Button click uses latest form state. |
forkJoin | Waits for all inner Observables to complete, then emits final values. | Parallel HTTP calls needed once. |
zip | Pairs values by emission order. | Step-by-step pair matching. |
merge | Runs streams together and emits as values arrive. | Combine multiple event sources. |
concat | Runs streams one after another. | Sequence tasks in order. |
race | Uses the stream that emits first. | Timeout or fastest source wins. |
viewModel$ = combineLatest([
users$,
searchTerm$,
selectedRole$
]).pipe(
map(([users, term, role]) => ({
users: users.filter(user =>
user.name.includes(term) && (!role || user.role === role)
),
term,
role
}))
);
Flattening Operators
| Operator | Behavior | Best use | Avoid when |
|---|---|---|---|
switchMap | Cancels previous inner Observable and switches to latest. | Search, route param HTTP calls, latest result wins. | Every request must finish. |
mergeMap | Runs inner Observables concurrently. | Independent writes, parallel requests, event fan-out. | Order matters or concurrency must be limited. |
concatMap | Queues inner Observables and runs one at a time. | Ordered saves, sequential jobs, queue behavior. | Latest request should cancel old one. |
exhaustMap | Ignores new source values while inner Observable is active. | Login, payment, submit button duplicate prevention. | User should be able to cancel and start a newer request. |
saveClicks$ = fromEvent(saveButton, 'click').pipe(
exhaustMap(() => this.api.saveForm(this.form.value).pipe(
catchError(error => {
this.toast.error('Save failed');
return EMPTY;
})
))
);
Utility and Side-Effect Operators
| Operator | Explanation | Example use |
|---|---|---|
tap | Runs side effects without changing value. | Logging, analytics, debugging. |
delay | Delays emissions. | Demo loading, backoff sketch. |
timeout | Errors if source takes too long. | Fail slow request with fallback. |
startWith | Emits an initial value before source values. | Initial loading or default UI state. |
endWith | Emits value after source completes. | Completion marker. |
defaultIfEmpty | Emits fallback if source completes without value. | No result fallback. |
isEmpty | Emits whether source had no values. | Empty-state detection. |
Error Handling Operators
| Operator | Explanation | Important rule |
|---|---|---|
catchError | Catches an error and returns a replacement Observable. | Always return an Observable. |
retry | Retries failed source a fixed number of times. | Use only when retry is safe. |
retryWhen | Custom retry strategy using another stream. | Useful for backoff logic. |
finalize | Runs cleanup on complete, error, or unsubscribe. | Good for loading flags. |
users$ = this.http.get<User[]>('/api/users').pipe(
retry({ count: 2, delay: 500 }),
catchError(error => {
this.logger.error(error);
return of([]);
}),
finalize(() => this.loading.set(false))
);
Multicasting and Caching
| Tool | Explanation | Use carefully |
|---|---|---|
Subject | Manual multicast source. | Can become global mutable state. |
BehaviorSubject | Stores latest value and emits it to new subscribers. | Good for current state. |
ReplaySubject | Replays previous values. | Always bound buffer size/time if possible. |
share | Shares one subscription while subscribers exist. | Good for avoiding duplicated side effects. |
shareReplay | Shares subscription and replays cached values. | Use correct reset/refCount behavior to avoid stale cache. |
Angular Operator Patterns
- Search field:
map->debounceTime->distinctUntilChanged->switchMap. - Route data:
paramMap->map->switchMap. - Save button: click stream ->
exhaustMap. - Ordered autosave: form value stream ->
concatMap. - Dashboard view model:
combineLatestof data, filters, and selected state. - Manual subscription cleanup:
takeUntilDestroyedor framework lifecycle helper. - Template rendering: prefer async pipe instead of manual subscribe when possible.
Common Operator Mistakes
- Using
mergeMapfor search, causing older responses to overwrite newer results. - Using
switchMapfor writes that must all complete. - Putting
catchErrorat the end when only an inner request should recover. - Forgetting to return an Observable from
catchError. - Using unbounded
ReplaySubjectand retaining too much memory. - Creating duplicate HTTP requests because a cold Observable is subscribed multiple times.
- Hiding business logic in
tapinstead of keeping it explicit. - Manually subscribing in Angular when the async pipe would handle lifecycle.
25 Most Important Practice Questions
1. Which operator transforms each value?
Answer:
map.2. Which operator filters values by condition?
Answer:
filter.3. Which operator waits for typing to stop?
Answer:
debounceTime.4. Which operator skips repeated consecutive values?
Answer:
distinctUntilChanged.5. Which operator cancels old HTTP requests in search?
Answer:
switchMap.6. Which operator runs inner streams concurrently?
Answer:
mergeMap.7. Which operator queues async work in order?
Answer:
concatMap.8. Which operator ignores duplicate submits while one request is active?
Answer:
exhaustMap.9. Which operator combines latest values from multiple streams?
Answer:
combineLatest.10. Which operator waits for multiple HTTP requests to complete once?
Answer:
forkJoin.11. Which operator samples latest state when a button is clicked?
Answer:
withLatestFrom.12. Which operator runs side effects without changing values?
Answer:
tap.13. Which operator catches errors?
Answer:
catchError.14. What must catchError return?
Answer: An Observable, such as
of([]) or throwError().15. Which operator runs cleanup on complete, error, or unsubscribe?
Answer:
finalize.16. Which operator retries failed source?
Answer:
retry.17. Which operator provides custom retry strategy?
Answer:
retryWhen.18. Which operator emits an initial value?
Answer:
startWith.19. Which operator accumulates state over time?
Answer:
scan.20. Which operator compares previous and current value?
Answer:
pairwise.21. Which operator batches values by time?
Answer:
bufferTime.22. Which operator shares one subscription?
Answer:
share.23. Which operator shares and replays cached values?
Answer:
shareReplay.24. Which creation operator listens to DOM events?
Answer:
fromEvent.25. Which creation operator emits values every interval?
Answer:
interval.