38 questions - Intermediate

RxJS interview questions and answers

RxJS is where Angular interviews get hard. Interviewers usually ask four things: how an Observable differs from a Promise, the difference between the four flattening operators, Subject types, and how you avoid memory leaks. Learn those properly and you will handle the rest.

Observables and the basics

What is RxJS?

RxJS is a library for working with streams of values over time. Instead of asking "what is the value now", you describe what should happen every time a new value arrives. It is the backbone 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, then either complete or error - and 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 runs immediately, 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 transformed with operators.

What does lazy mean here?

The function inside the Observable does not run until subscribe is called. So an HTTP Observable that nobody subscribes to never sends a request - a very common surprise for 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 creates a new producer for every subscriber, so each one gets its own independent run - an HTTP call is cold and two subscribers cause two requests. A hot Observable shares one producer, so all subscribers see the same values - a Subject or a DOM event stream is hot.

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

With a multicasting operator such as share or shareReplay. shareReplay also stores the last values so a late subscriber still receives them, which 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 immediately, and throwError errors immediately.

What is a pipe in RxJS?

pipe chains operators together. Each operator takes the stream, returns a new stream, and does not modify 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 transformation - it builds a new Observable and runs nothing. Subscribing is what actually 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 Subject that needs a starting value and always remembers the latest one, so any new subscriber immediately receives the current value. It is the standard way to hold state in 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?

It remembers a chosen number of past values (or a time window) and replays them to every new subscriber. Use it when late subscribers need history, not just the latest value.

What is an AsyncSubject?

It emits only the final value, and only when the subject completes. It is rarely used in application code.

When should you not use a Subject?

When a plain Observable would do. Exposing a Subject publicly lets any code push values into your state from anywhere, which makes bugs 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 is meant only for @Output bindings between a child and its parent. Using it as a general event bus inside services is discouraged; use a Subject there.

Operators you must know

What is the difference between map and switchMap?

map transforms each value into another plain value. switchMap transforms each value into a new Observable and subscribes to it, flattening the result. Use map for data, 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 - best for search-as-you-type. mergeMap runs all of them at once with no order guarantee - best for independent parallel work. concatMap queues them and runs one at a time in order - best when order matters. exhaustMap ignores new values while one is still running - best for a submit button, so a double click does not 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 aborted mid-flight and you may end up with half-saved data. 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 - good for typing. throttleTime emits immediately and then ignores values for a period - good for scroll and resize.

What is distinctUntilChanged?

It drops a value that is the same as the previous one, so nothing downstream runs again for no reason. For objects, pass a compare function because the default is reference comparison.

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

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

What is the difference between merge and concat?

merge subscribes to all sources at once and interleaves the values. concat subscribes to the next source only after the previous one has completed, so the order is strict.

What does startWith do?

It emits a value immediately before the source starts. It is a neat way to show a loading state or a default value while waiting for real data.

What does the tap operator do?

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

What is the difference between take, first and takeUntil?

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

What is scan and how is it different from reduce?

Both accumulate values. scan emits the running result after every value, so you can show live totals. reduce emits only the final result 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 newcomers. Without refCount: true, the underlying subscription stays alive forever even after everyone unsubscribes, which leaks - especially with an interval source.

Errors, retries and testing

How do you handle errors in RxJS?

With catchError inside the pipe. Return a fallback Observable such as of([]) to recover, or rethrow with throwError to let the caller deal with it. Remember that an error terminates 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 is a terminal event. This bites hardest when catchError is placed 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 request fails.

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

What is the difference between retry and retryWhen?

retry(n) resubscribes immediately up to n times. retryWhen (now usually written as retry with a delay config) lets you decide when to retry, for example with an increasing backoff, and to give up after a limit.

What does the finalize operator do?

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

What causes memory leaks in RxJS?

Subscribing without ever unsubscribing to a stream that never completes - an interval, a Subject, a router or form event stream. The component is destroyed but the callback keeps running and holding 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. Also 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 creates 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 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 assert the exact timing of emissions.

What is a scheduler in RxJS?

It controls when a subscription starts and when values are delivered - synchronously, on a microtask, on a timer, or on animation frames. Most code never sets one, but tests use the TestScheduler to make time deterministic.

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.