map(), filter(), and tap() in RxJS
These are three commonly used RxJS operators.
The easiest way to remember them is:
| Operator | Purpose | Changes value? |
|---|---|---|
map() |
Transform a value | Yes |
filter() |
Select/skip values | No |
tap() |
Observe / side effect | No |
Observable
│
▼
filter() → Should this value continue?
│
▼
map() → Change the value
│
▼
tap() → Observe/log/do a side effect
│
▼
Subscriber
1. What Does map() Do?
What is map()?
map() is an RxJS transformation operator.
It takes each value emitted by an Observable, transforms it into another value, and emits the transformed value.
map()= Take a value → change it → emit the changed value.
Full Simple Example
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
const numbers$ = of(1, 2, 3, 4, 5);
numbers$
.pipe(
map(number => number * 10)
)
.subscribe(value => {
console.log(value);
});
Output
10
20
30
40
50
Explanation
Original Observable:
1 → 2 → 3 → 4 → 5
Operator:
map(number => number * 10)
Each value is transformed:
1 × 10 = 10
2 × 10 = 20
3 × 10 = 30
4 × 10 = 40
5 × 10 = 50
Flow:
1 → 2 → 3 → 4 → 5
│
▼
map(x => x * 10)
│
▼
10 → 20 → 30 → 40 → 50
map() with Angular HttpClient
Suppose the API returns:
[
{ id: 1, name: 'Laptop', price: 75000 },
{ id: 2, name: 'Mouse', price: 1000 },
{ id: 3, name: 'Keyboard', price: 2500 }
]
You only want product names.
getProductNames() {
return this.http
.get<Product[]>('/api/products')
.pipe(
map(products =>
products.map(product => product.name)
)
);
}
Result:
[
'Laptop',
'Mouse',
'Keyboard'
]
So:
Observable<Product[]>
│
▼
map()
│
▼
Observable<string[]>
Notice that map() can even change the type emitted by the Observable.
2. What Does filter() Do?
What is filter()?
filter() is an RxJS filtering operator.
It allows a value to continue only when the specified condition returns true.
filter()= Check condition → keep or skip the value.
Full Simple Example
import { of } from 'rxjs';
import { filter } from 'rxjs/operators';
const numbers$ = of(10, 20, 30, 40, 50);
numbers$
.pipe(
filter(number => number > 20)
)
.subscribe(value => {
console.log(value);
});
Output
30
40
50
Explanation
Original values:
10 → 20 → 30 → 40 → 50
Condition:
number > 20
Evaluation:
10 > 20 → false → Skip
20 > 20 → false → Skip
30 > 20 → true → Keep
40 > 20 → true → Keep
50 > 20 → true → Keep
Therefore:
10 → 20 → 30 → 40 → 50
│
▼
filter(x > 20)
│
▼
30 → 40 → 50
Important: RxJS filter() Filters Emissions
This point is important.
Consider:
of(10, 20, 30, 40)
.pipe(
filter(x => x > 20)
);
Here the Observable emits individual numbers:
10
20
30
40
So RxJS filter() checks each Observable emission.
But suppose an HTTP Observable emits an entire array:
Observable<Product[]>
Then:
filter(products => ...)
would be filtering whether the whole emitted array continues—not individual products.
To filter products inside that array, you'd commonly use RxJS map() plus JavaScript array filter():
this.http.get<Product[]>('/api/products')
.pipe(
map(products =>
products.filter(product => product.price > 5000)
)
);
There are two different operations here:
map(...) // RxJS operator
products.filter(...) // JavaScript Array method
This distinction is frequently asked in interviews.
3. What Does tap() Do?
What is tap()?
tap() is an RxJS operator used to perform a side effect without normally modifying the emitted value.
Common uses include:
- Logging
- Debugging
- Inspecting values
- Setting certain state
- Observing errors/completion
- Tracing execution
tap()= Look at the value / perform a side effect, but let the original value continue.
Full Simple Example
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';
const numbers$ = of(10, 20, 30);
numbers$
.pipe(
tap(number => {
console.log('Inside tap:', number);
})
)
.subscribe(value => {
console.log('Subscriber:', value);
});
Output
Inside tap: 10
Subscriber: 10
Inside tap: 20
Subscriber: 20
Inside tap: 30
Subscriber: 30
Notice:
10 → tap() → 10
20 → tap() → 20
30 → tap() → 30
The values weren't transformed.
map() vs tap()
This is a very important distinction.
map() changes the emitted value
of(10)
.pipe(
map(x => x * 2)
)
.subscribe(x => console.log(x));
Output:
20
Flow:
10
│
▼
map(x * 2)
│
▼
20
tap() observes without transforming
of(10)
.pipe(
tap(x => console.log('Received:', x))
)
.subscribe(x => console.log(x));
Subscriber still receives:
10
Flow:
10
│
▼
tap()
│
│ console.log(10)
│
▼
10
│
▼
Subscriber
Therefore:
map() → Transform
tap() → Side effect / observe
4. Full Example Using All Three
This example makes the difference very clear.
import { of } from 'rxjs';
import { filter, map, tap } from 'rxjs/operators';
const numbers$ = of(1, 2, 3, 4, 5);
numbers$
.pipe(
filter(number => number > 2),
map(number => number * 10),
tap(number => {
console.log('After map:', number);
})
)
.subscribe(value => {
console.log('Subscriber:', value);
});
Step 1 — Original
1 → 2 → 3 → 4 → 5
Step 2 — filter()
filter(number => number > 2)
Result:
3 → 4 → 5
Step 3 — map()
map(number => number * 10)
Result:
30 → 40 → 50
Step 4 — tap()
tap(number => {
console.log('After map:', number);
})
It logs the values but doesn't transform them.
Step 5 — subscribe()
Receives:
30
40
50
Complete flow:
1 → 2 → 3 → 4 → 5
│
▼
filter(x > 2)
│
▼
3 → 4 → 5
│
▼
map(x * 10)
│
▼
30 → 40 → 50
│
▼
tap()
log each value
│
▼
30 → 40 → 50
│
▼
subscribe()
5. Real Angular HTTP Example
Suppose:
getProducts(): Observable<Product[]> {
return this.http
.get<Product[]>('/api/products')
.pipe(
tap(products => {
console.log('API returned:', products);
}),
map(products =>
products.filter(product => product.price > 5000)
),
tap(products => {
console.log('Filtered products:', products);
})
);
}
Here:
First tap()
tap(products => {
console.log('API returned:', products);
})
Just logs the original response.
map()
map(products =>
products.filter(product => product.price > 5000)
)
Transforms the emitted product array into a filtered array.
Second tap()
tap(products => {
console.log('Filtered products:', products);
})
Inspects the transformed result.
Flow:
Web API
│
▼
Product[]
│
▼
tap()
Log original response
│
▼
map()
Filter array
│
▼
Filtered Product[]
│
▼
tap()
Log filtered response
│
▼
Subscriber
6. Easy Real-World Analogy
Imagine an airport security line.
filter()
Decides who is allowed through:
All passengers
│
▼
filter()
│
├── Allowed → Continue
└── Not allowed → Stop
map()
Changes/transforms something:
Passenger
│
▼
map()
│
▼
Passenger + Boarding Pass
tap()
Security camera observes what passes:
Passenger
│
▼
tap()
Record/log
│
▼
Same Passenger
So remember:
filter → SELECT
map → TRANSFORM
tap → OBSERVE
Key Points
map()is a transformation operator.filter()is a filtering operator.tap()is mainly used for side effects.map()changes/transforms emitted values.filter()determines whether each emission continues.tap()observes values without normally transforming them.- Operators are commonly used inside
pipe(). - Operator order matters.
- RxJS
filter()filters Observable emissions. - JavaScript Array
.filter()filters elements inside an array. tap()is especially useful for logging and debugging.map()can change the emitted type, such asProduct[]→string[].
Remember this
filter() → Should it continue?
map() → What should it become?
tap() → What is happening?
Interview Questions and Answers
1. What does map() do in RxJS?
Answer:map() transforms every value emitted by an Observable and emits the transformed value.
map(x => x * 10)
2. What does filter() do?
Answer:filter() allows an emitted value to continue only when its condition returns true.
filter(x => x > 10)
3. What does tap() do?
Answer:tap() performs side effects such as logging or debugging without normally transforming the emitted value.
tap(x => console.log(x))
4. What is the difference between map() and tap()?
Answer:map() transforms the emitted value, whereas tap() observes or performs a side effect while allowing the value to continue unchanged.
map(10) → 20
tap(10) → log 10 → 10
5. What is the difference between map() and filter()?
Answer:map() changes a value, whereas filter() determines whether the value should continue.
map() → Transform
filter() → Select
6. Does filter() change the value?
Answer:
No. It determines whether the emitted value continues downstream. If the condition is true, the value continues; otherwise it is skipped.
7. Does tap() modify the Observable value?
Answer:tap() is not intended for transforming values. Use map() when you need to transform the emitted value.
8. What is the difference between RxJS filter() and Array filter()?
Answer:
RxJS filter() filters Observable emissions, while JavaScript Array filter() filters elements within an array.
For an HTTP Observable<Product[]>, this is common:
map(products =>
products.filter(product => product.price > 5000)
)
Here map() is an RxJS operator, while products.filter() is an Array method.
9. Can we use map(), filter(), and tap() together?
Answer:
Yes. Multiple operators can be composed with pipe():
observable$
.pipe(
filter(x => x > 2),
map(x => x * 10),
tap(x => console.log(x))
)
.subscribe(x => console.log(x));
10. Short Interview Answer
filter()decides which Observable emissions continue,map()transforms those emissions, andtap()performs side effects such as logging without normally changing the emitted value.