← Back to Articles         
What are RxJS operators?

What are RxJS operators?

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

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 example map, filter, switchMap, debounceTime, catchError, and retry.

← Back to Articles