What Are RxJS Operators?
1. What are RxJS Operators?
RxJS operators are functions used to process, transform, filter, combine, control, or handle values emitted by an Observable.
They allow you to take an Observable:
Observable<T>
and create a new Observable with modified behavior or values.
Simple definition
RxJS operators are functions that manipulate an Observable stream before its values reach the subscriber.
For example:
of(1, 2, 3, 4, 5)
produces:
1 → 2 → 3 → 4 → 5
We can apply:
filter()
to keep only values greater than 2:
3 → 4 → 5
Then apply:
map()
to multiply them by 10:
30 → 40 → 50
The complete flow:
Observable
1 → 2 → 3 → 4 → 5
│
▼
filter(x > 2)
│
▼
3 → 4 → 5
│
▼
map(x * 10)
│
▼
30 → 40 → 50
│
▼
Subscriber
2. Full Simple Sample Program
Let's use filter() and map().
app.ts
import { Component } from '@angular/core';
import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Component({
selector: 'app-root',
standalone: true,
templateUrl: './app.html'
})
export class App {
results: number[] = [];
constructor() {
const numbers$ = of(1, 2, 3, 4, 5);
numbers$
.pipe(
filter(number => number > 2),
map(number => number * 10)
)
.subscribe({
next: value => {
this.results.push(value);
},
complete: () => {
console.log('Completed');
}
});
}
}
app.html
<h2>RxJS Operators</h2>
@for (result of results; track $index) {
<p>{{ result }}</p>
}
Output
30
40
50
3. Program Explanation
First, we create an Observable:
const numbers$ = of(1, 2, 3, 4, 5);
It emits:
1 → 2 → 3 → 4 → 5
Then:
.pipe(...)
is used to apply operators.
numbers$
.pipe(
filter(...),
map(...)
)
Finally:
.subscribe(...)
receives the processed values.
So the basic pattern is:
observable$
.pipe(
operator1(),
operator2(),
operator3()
)
.subscribe(...);
Think of it as:
Observable
│
▼
pipe()
│
├── operator 1
│
├── operator 2
│
└── operator 3
│
▼
Subscriber
4. What Does filter() Do?
filter() allows only values satisfying a condition to continue.
filter(number => number > 2)
Input:
1 → 2 → 3 → 4 → 5
Output:
3 → 4 → 5
It is similar conceptually to filtering a collection, but here we're filtering Observable emissions.
5. What Does map() Do?
map() transforms each emitted value.
map(number => number * 10)
Input:
3 → 4 → 5
Output:
30 → 40 → 50
So:
map()
means:
Take each emitted value and convert it into another value.
6. What is pipe()?
Operators are commonly composed using:
pipe()
For example:
numbers$
.pipe(
filter(x => x > 2),
map(x => x * 10)
)
pipe() applies the operators in order.
Order matters.
Observable
↓
filter()
↓
map()
↓
Subscriber
The output of one stage becomes the input to the next.
7. pipe() vs Operator vs subscribe()
These three concepts are important.
Observable
Produces the values:
const numbers$ = of(1, 2, 3);
pipe()
Builds the processing pipeline:
numbers$.pipe(...)
Operators
Process the stream:
map(...)
filter(...)
subscribe()
Consumes the resulting stream:
.subscribe(value => {
console.log(value);
});
So:
Observable
│
▼
pipe()
│
├── filter()
│
└── map()
│
▼
subscribe()
An easy way to remember:
Observable produces → Operators process → Subscriber consumes.
8. Common RxJS Operators in Angular
There are many RxJS operators. For Angular development, these are among the most important:
| Operator | Purpose |
|---|---|
map() |
Transform values |
filter() |
Allow values matching a condition |
tap() |
Perform side effects/debugging |
debounceTime() |
Wait before processing rapid emissions |
distinctUntilChanged() |
Ignore consecutive duplicate values |
switchMap() |
Switch to latest inner Observable |
mergeMap() |
Run inner Observables concurrently |
concatMap() |
Run inner Observables sequentially |
catchError() |
Handle errors |
retry() |
Retry failed operations |
take() |
Take limited number of values |
takeUntil() |
Continue until another Observable emits |
finalize() |
Run cleanup when stream terminates |
combineLatest() |
Combine latest values from streams |
You will frequently encounter these in Angular applications.
9. map() — Transform Data
Suppose the API returns products, but you only need their names.
this.http.get<Product[]>('/api/products')
.pipe(
map(products =>
products.map(product => product.name)
)
)
.subscribe(names => {
console.log(names);
});
If the API returns:
Laptop
Mouse
Keyboard
map() can transform the response into:
[
'Laptop',
'Mouse',
'Keyboard'
]
10. filter() — Filter Emissions
Example:
of(10, 20, 30, 40)
.pipe(
filter(value => value > 20)
)
.subscribe(value => {
console.log(value);
});
Output:
30
40
Flow:
10 → 20 → 30 → 40
│
▼
filter(x > 20)
│
▼
30 → 40
11. tap() — Perform Side Effects
tap() is commonly used for:
- logging
- debugging
- observing intermediate values
- certain side effects
Example:
this.http.get<Product[]>('/api/products')
.pipe(
tap(products => {
console.log('API Response:', products);
})
)
.subscribe(products => {
this.products = products;
});
Unlike map(), tap() normally doesn't transform the emitted value.
Think:
map() → change the value
tap() → look at/do something with the value
12. catchError() — Handle Errors
HTTP requests can fail.
import { catchError, of } from 'rxjs';
this.http.get<Product[]>('/api/products')
.pipe(
catchError(error => {
console.error(error);
return of([]);
})
)
.subscribe(products => {
this.products = products;
});
If the API fails, we return:
[]
instead of allowing that particular pipeline to propagate the original error.
Flow:
HTTP Request
│
├── Success ─────► products
│
└── Error
│
▼
catchError()
│
▼
[]
13. retry() — Retry Failed Operations
Example:
import { retry } from 'rxjs';
this.http.get<Product[]>('/api/products')
.pipe(
retry(2)
)
.subscribe(products => {
this.products = products;
});
If the request fails, RxJS can retry it.
Conceptually:
Request
│
▼
Failed
│
▼
Retry #1
│
▼
Failed
│
▼
Retry #2
retry() should mainly be used for appropriate transient failures, not blindly for every client/server error.
14. debounceTime() — Wait Before Processing
This is extremely useful for search boxes.
Suppose the user types:
A
An
Ang
Angu
Angular
Without debounce, you could trigger many API calls.
this.searchControl.valueChanges
.pipe(
debounceTime(300)
)
.subscribe(search => {
console.log(search);
});
debounceTime(300) waits until the user stops emitting new values for roughly 300 milliseconds.
A ---- An ---- Ang ---- Angular
│
wait 300ms
│
▼
Angular
15. distinctUntilChanged() — Ignore Consecutive Duplicates
Example:
this.searchControl.valueChanges
.pipe(
distinctUntilChanged()
)
.subscribe(value => {
console.log(value);
});
If values are:
Angular
Angular
Angular
React
React
Vue
then consecutive duplicates are suppressed:
Angular
React
Vue
This is often combined with:
debounceTime()
16. switchMap() — Switch to Latest Observable
This is one of the most important Angular RxJS operators.
Consider search:
this.searchControl.valueChanges
.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(search =>
this.productService.search(search)
)
)
.subscribe(products => {
this.products = products;
});
Suppose the user types:
Lap
Laptop
The first request starts:
GET /api/products?search=Lap
Then the user changes the value:
Laptop
A newer request starts:
GET /api/products?search=Laptop
switchMap() unsubscribes from the previous inner Observable and switches to the latest one.
Lap
│
▼
Request #1
X cancelled/unsubscribed
Laptop
│
▼
Request #2
│
▼
Latest Result
This helps prevent stale search results.
17. mergeMap() vs concatMap() vs switchMap()
These are important higher-order mapping operators.
switchMap()
Previous work → replaced by latest
Useful for:
Search
Autocomplete
Route-driven requests
mergeMap()
Request 1 ─────►
Request 2 ─────► run concurrently
Request 3 ─────►
Useful when operations can run concurrently and all results matter.
concatMap()
Request 1
↓
Complete
↓
Request 2
↓
Complete
↓
Request 3
Useful when order matters and operations should execute sequentially.
A useful comparison:
| Operator | Behavior |
|---|---|
switchMap() |
Latest wins |
mergeMap() |
Concurrent |
concatMap() |
Sequential |
18. take() — Limit Number of Values
Suppose:
interval(1000)
produces:
0 → 1 → 2 → 3 → 4 → 5 → ...
Use:
interval(1000)
.pipe(
take(3)
)
.subscribe(value => {
console.log(value);
});
Output:
0
1
2
Then the stream completes.
19. Real Angular Search Example
Here's a realistic combination of operators:
this.searchControl.valueChanges
.pipe(
debounceTime(300),
distinctUntilChanged(),
filter(search => search.length >= 2),
switchMap(search =>
this.productService.searchProducts(search)
),
catchError(error => {
console.error(error);
return of([]);
})
)
.subscribe(products => {
this.products = products;
});
Flow:
User Types
│
▼
debounceTime(300)
│
▼
distinctUntilChanged()
│
▼
filter(length >= 2)
│
▼
switchMap()
│
▼
HTTP Request
│
▼
catchError()
│
▼
Subscriber
This example shows why RxJS operators are so useful in Angular.
20. Operators Do Not Normally Modify the Original Observable
An important concept is that RxJS operators generally return a new Observable.
For example:
const numbers$ = of(1, 2, 3);
const doubled$ = numbers$.pipe(
map(x => x * 2)
);
Now conceptually:
numbers$
1 → 2 → 3
│
│ map(x * 2)
▼
doubled$
2 → 4 → 6
The operator creates a transformed Observable pipeline rather than changing numbers$ itself.
21. Main Categories of RxJS Operators
You don't need to memorize every operator. It is more useful to understand their purpose.
Transformation
Change values or map them to other Observables:
map
switchMap
mergeMap
concatMap
Filtering
Control which values continue:
filter
take
debounceTime
distinctUntilChanged
Error Handling
catchError
retry
Utility / Side Effects
tap
finalize
Combining Streams
combineLatest
forkJoin
zip
Key Points
- RxJS operators are functions used to process Observable streams.
- Operators can transform, filter, combine, delay, retry, or handle errors.
- Operators are commonly applied using
pipe(). - Operators generally return a new Observable.
map()transforms values.filter()filters emitted values.tap()performs side effects without normally changing values.catchError()handles Observable errors.retry()retries failed Observable executions.debounceTime()is useful for rapidly changing values such as search input.distinctUntilChanged()ignores consecutive duplicate values.switchMap()switches to the latest inner Observable.mergeMap()allows concurrent inner Observables.concatMap()processes inner Observables sequentially.take()limits the number of values received.
Easy way to remember
Observable
│
▼
pipe()
│
├── filter() → Select
├── map() → Transform
├── tap() → Observe / side effect
├── debounceTime() → Wait
├── switchMap() → Switch to latest
├── catchError() → Handle error
└── retry() → Try again
│
▼
subscribe()
Interview Questions and Answers
1. What are RxJS operators?
Answer:
RxJS operators are functions used to transform, filter, combine, control, or handle values emitted by Observables.
2. How are operators applied to an Observable?
Answer:
Operators are commonly composed using pipe().
observable$
.pipe(
filter(x => x > 10),
map(x => x * 2)
)
.subscribe(...);
3. What is the difference between map() and filter()?
Answer:map() transforms each emitted value, while filter() determines whether an emitted value should continue through the stream.
4. What is the difference between map() and tap()?
Answer:map() is used to transform values. tap() is mainly used for side effects such as logging and debugging without normally changing the emitted value.
5. What is debounceTime()?
Answer:debounceTime() waits for a specified period of silence before emitting the latest value. It is commonly used for search and autocomplete.
debounceTime(300)
6. What is distinctUntilChanged()?
Answer:
It prevents consecutive duplicate values from being emitted downstream.
It is commonly combined with debounceTime() for search inputs.
7. What is switchMap()?
Answer:switchMap() maps each source value to an inner Observable and switches to the latest one, unsubscribing from the previous inner Observable. It is commonly used for search and dependent HTTP calls.
8. switchMap() vs mergeMap() vs concatMap()?
Answer:
switchMap → Latest wins
mergeMap → Run concurrently
concatMap → Run sequentially
9. What does catchError() do?
Answer:catchError() intercepts an Observable error and lets you recover, replace the stream, or rethrow an error.
catchError(error => {
console.error(error);
return of([]);
})
10. Do RxJS operators modify the original Observable?
Answer:
Generally, no. Operators create and return a new Observable representing the transformed pipeline.
11. What are the most important RxJS operators for Angular interviews?
A strong core set is:
map()
filter()
tap()
debounceTime()
distinctUntilChanged()
switchMap()
mergeMap()
concatMap()
catchError()
retry()
take()
takeUntil()
combineLatest()
forkJoin()
12. Short Interview Answer
RxJS operators are functions that process Observable streams. They are composed with
pipe()to transform, filter, combine, control, and handle emitted values—for examplemap,filter,switchMap,debounceTime,catchError, andretry.