← Back to Articles         
map(), filter(), and tap() in RxJS

map(), filter(), and tap() in RxJS

Published on 01 Oct 2026     8 min read Angular
RxJS & Observables

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 as Product[] → 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, and tap() performs side effects such as logging without normally changing the emitted value.

← Back to Articles