38 questions - Intermediate

RxJS interview questions and answers

RxJS is where Angular interviews get hard. Interviewers usually ask four things: how an Observable is different from a Promise, the four flattening operators, the Subject types, and how you avoid memory leaks. Learn those four well and the rest follows.

Last updated:

Observables and the basics

What is RxJS?

RxJS is a library for working with values that arrive over time. Instead of asking "what is the value now", you say what should happen each time a new value arrives. It is the base of Angular HTTP, forms and routing.

What is an Observable?

An Observable is a plan for producing values. Nothing happens until someone subscribes. It can emit zero, one or many values, and then it either completes or errors. After complete or error, it never emits again.

const numbers$ = new Observable((subscriber) => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.complete();
});

numbers$.subscribe({
  next: (v) => console.log(v),
  error: (e) => console.error(e),
  complete: () => console.log("done"),
});

What is the difference between an Observable and a Promise?

A Promise starts at once, gives exactly one value, and cannot be cancelled. An Observable is lazy, can give many values over time, can be cancelled by unsubscribing, and can be retried and changed with operators.

What does lazy mean here?

The function inside the Observable does not run until you call subscribe. So an HTTP Observable that nobody subscribes to never sends a request. This surprises many beginners.

What is an Observer and a Subscription?

An Observer is the object with next, error and complete that receives the values. A Subscription is what subscribe returns. Calling unsubscribe on it stops the stream and frees resources.

What is the difference between a hot and a cold Observable?

A cold Observable makes a new producer for every subscriber, so each one gets its own run. An HTTP call is cold, so two subscribers cause two requests. A hot Observable shares one producer, so every subscriber sees the same values. A Subject or a DOM event stream is hot.

How do you turn a cold Observable into a hot one?

Use a multicasting operator such as share or shareReplay. shareReplay also keeps the last values, so a late subscriber still gets them. That is the usual way to cache an HTTP result.

readonly config$ = this.http.get<Config>("/api/config").pipe(
  shareReplay({ bufferSize: 1, refCount: true })
);

What are the common creation functions?

of emits the values you give and completes. from turns an array, promise or iterable into a stream. interval and timer emit on a schedule. fromEvent listens to DOM events. EMPTY completes at once, and throwError errors at once.

What is a pipe in RxJS?

pipe joins operators together. Each operator takes the stream and returns a new stream. It never changes the original one. Reading a pipe from top to bottom tells you exactly what happens to each value.

What is the difference between an operator and a subscription?

An operator only describes a change. It builds a new Observable and runs nothing. Subscribing is what really starts the work.

Subjects

What is a Subject?

A Subject is both an Observable and an Observer. You can call next on it to push values, and other code can subscribe to it. It is multicast, so every subscriber gets the same value.

const clicks$ = new Subject<string>();
clicks$.subscribe((v) => console.log("A", v));
clicks$.next("save");   // A save

What is a BehaviorSubject?

A BehaviorSubject needs a starting value and always keeps the latest one. Any new subscriber gets the current value at once. It is the standard way to hold state inside a service.

private user$ = new BehaviorSubject<User | null>(null);
readonly currentUser$ = this.user$.asObservable();

setUser(u: User) { this.user$.next(u); }

What is a ReplaySubject?

A ReplaySubject keeps a chosen number of past values, or a time window, and replays them to every new subscriber. Use it when late subscribers need the history, not only the latest value.

What is an AsyncSubject?

An AsyncSubject emits only the last value, and only when the subject completes. It is rarely used in app code.

When should you not use a Subject?

When a plain Observable is enough. If you expose a Subject publicly, any code can push values into your state from anywhere, and bugs become very hard to trace. Keep the Subject private and expose it with asObservable().

What is the difference between a Subject and an EventEmitter in Angular?

EventEmitter extends Subject, but it is meant only for @Output between a child and its parent. Using it as a general event bus inside services is not advised. Use a Subject there.

Operators you must know

What is the difference between map and switchMap?

map changes each value into another plain value. switchMap changes each value into a new Observable, subscribes to it, and flattens the result. Use map for data. Use switchMap when each value starts another async job.

Explain switchMap, mergeMap, concatMap and exhaustMap.

switchMap cancels the previous inner Observable when a new value arrives, so it fits search as you type. mergeMap runs all of them at the same time with no order, so it fits independent parallel work. concatMap queues them and runs one at a time in order, so it fits work where order matters. exhaustMap ignores new values while one is still running, so it fits a submit button and a double click cannot save twice.

// search box: cancel the old request
search$.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap((q) => this.api.search(q))
);

// save button: ignore extra clicks
save$.pipe(exhaustMap(() => this.api.save(form.value)));

Why is switchMap dangerous for a save request?

Because it cancels the previous one. If the user clicks save twice quickly, the first save is stopped in the middle, and the data can end up half saved. Use concatMap or exhaustMap for writes.

What do debounceTime and throttleTime do?

debounceTime waits until the stream has been quiet for the given time and then emits the last value. It fits typing. throttleTime emits at once and then ignores values for a period. It fits scroll and resize.

What is distinctUntilChanged?

It drops a value that is the same as the one before it, so nothing after it runs for no reason. For objects, pass a compare function, because the default compares references.

What is the difference between combineLatest, forkJoin, zip and withLatestFrom?

combineLatest emits when any source emits, using the latest of each, but only after every source has emitted once. forkJoin waits for all sources to complete and emits the final values once, like Promise.all. zip pairs values by position. withLatestFrom emits only when the main source emits, and takes the latest of the others.

What is the difference between merge and concat?

merge subscribes to all sources at once and mixes the values together. concat subscribes to the next source only after the previous one completes, so the order is strict.

What does startWith do?

It emits a value at once, before the source starts. It is a neat way to show a loading state or a default value while the real data is on the way.

What does the tap operator do?

tap lets you do a side effect, like logging or setting a loading flag, without changing the values. If your tap is changing the data, you should be using map.

What is the difference between take, first and takeUntil?

take(n) takes n values and then completes. first() takes one, and errors if the source completes without emitting. takeUntil completes the stream when another Observable emits. That is the classic unsubscribe pattern.

What is scan and how is it different from reduce?

Both build up a result. scan emits the running result after every value, so you can show a live total. reduce emits only the final result, and only when the source completes.

clicks$.pipe(scan((count) => count + 1, 0)); // 1, 2, 3...

What does shareReplay do and what is the refCount trap?

shareReplay shares one subscription and replays the last values to new subscribers. Without refCount: true, the subscription underneath stays alive forever, even after everyone unsubscribes. That leaks, above all with an interval source.

Errors, retries and testing

How do you handle errors in RxJS?

Use catchError inside the pipe. Return a fallback Observable such as of([]) to recover, or throw again with throwError so the caller handles it. Remember that an error ends the stream.

this.http.get<User[]>("/api/users").pipe(
  catchError((err) => {
    console.error(err);
    return of([]);
  })
);

Why does my stream stop working after one error?

Because an error ends the stream for good. This hurts most when catchError sits on the outer stream of a valueChanges pipeline: one failed request kills the whole search box. Put catchError on the inner Observable inside switchMap, so only that one request fails.

search$.pipe(
  switchMap((q) => this.api.search(q).pipe(catchError(() => of([]))))
);

What is the difference between retry and retryWhen?

retry(n) subscribes again at once, up to n times. retryWhen, now usually written as retry with a delay config, lets you decide when to try again, for example with a growing wait, and when to give up.

What does the finalize operator do?

finalize runs a function when the stream completes, errors or is unsubscribed. It is the right place to hide a loading spinner, because it fires in all three cases.

What causes memory leaks in RxJS?

Subscribing and never unsubscribing from a stream that never completes: an interval, a Subject, a router event or a form event stream. The component is destroyed, but the callback keeps running and keeps the old component in memory.

What are the ways to unsubscribe properly?

Best: let the AsyncPipe do it in the template. In code: takeUntilDestroyed() in Angular, or a destroy$ Subject with takeUntil, or collect subscriptions in one Subscription object and unsubscribe in ngOnDestroy. Note that take(1) and streams that complete clean themselves up.

private destroy$ = new Subject<void>();

ngOnInit() {
  this.service.data$.pipe(takeUntil(this.destroy$)).subscribe();
}
ngOnDestroy() {
  this.destroy$.next();
  this.destroy$.complete();
}

Why is subscribing inside a subscribe a bad idea?

It makes nested callbacks that are hard to cancel, hard to read and easy to leak. Use a flattening operator like switchMap or concatMap instead.

What is a marble diagram?

A marble diagram is a simple text or picture that shows values on a time line, like "--a--b--|". It is used to explain operators and to write tests with the TestScheduler, where you check the exact timing of each value.

What is a scheduler in RxJS?

A scheduler controls when a subscription starts and when values are delivered: at once, on a microtask, on a timer, or on animation frames. Most code never sets one. Tests use the TestScheduler to make time predictable.

How do you convert between Observables and Promises?

firstValueFrom or lastValueFrom turn an Observable into a Promise. from turns a Promise into an Observable. Avoid the old toPromise, which is removed in RxJS 8.

const user = await firstValueFrom(this.api.getUser(id));

Continue with another topic

Practise live

CrackInterviewAI listens to the live interview and gives a structured answer on screen for coding, system design, HR and project questions. Download for Windows.